best practices Google+: amazing google interactive posts sharing
by gguuss
leave a comment
Sharing: Best practices for targeting Interactive Posts
Yesterday, Joanna and I gave a talk on best practices for the Google+ platform. We discussed just a couple subtle ways that you can make the most of the features that we’ve shipped with the Google+ Sign-In button. In this post, I share a few thoughts on interactive posts, the newest way of sharing on Google+, and a brief look into taking full advantage of targeting posts to specific recipients.
There are two types of sharing available on Google+:
This post is focused on the latter, Interactive posts.
What are Interactive posts?
I like to think of Interactive Posts as a number of things:
- A better way of sharing
- A way of enabling interactions with your online presence
- A way of creating experiences both within and outside the Google+ social stream
- A way of targeting shares to bring people into your apps
Put concisely, the interactive post is a greatly enhanced version of our existing share button that has specifically been updated for the recent release incorporating sign-in for 3rd party web sites that most importantly allows you to generate notifications across Google.
You now can do the following things that you could not before:
- Target posts to specific people
- Prefill text
- Link directly within your apps and sites from the posts in the Google+ stream
- Show different labels within the post other than share
- Include deep links for mobile devices
Now, think about it for a second. The updated functionality of this feature wasn’t chosen at random. Each of these pieces of Interactive Posts is there to let you tailor the content to allow you to make the MOST out of this feature. In this post, I’m going to show you a small way that you can take advantage of just one of these features, the recipients field.
Demo
Enough about what it is, how about a quick demo? Look at a demo of interactive posts. You will notice that the box behaves very similarly to a share. However, the recipients of the post are specified so that each person receives notifications across Google. This is huge because it allows you to create experiences within your application that bring in users to the social experience and allows you to choose the relevant people.
For example, if you have a game where people need to draw a coalition of others to win, you can target your posts to those specific people who you believe the current user can influence.
Code
Let’s talk about interactive posts and how they work:
First, you configure your interactive post by taking a div or other HTML entity, such as the following:
<div id="sharePost"><button>Attack</button></div>
Next, using JavaScript, you configure the post with a content URL, call to action label, and other properties, inherited from the Google+ Sign-in button:
var options = {
contenturl: 'https://blog.gusclass.com',
contentdeeplinkid: '/',
clientid: clientId,
prefilltext: 'I love reading Gus's blog, you should add it to your blog reader.',
calltoactionlabel: 'SUBSCRIBE',
calltoactionurl: 'http://gusclass.com/blog/?feed=rss2',
requestvisibleactions: '',
cookiepolicy: 'http://gusclass.com',
recipients: '109716647623830091721, io2013@gusclass.com'
};
gapi.interactivepost.render(divId, options);
Then, using JavaScript, you call gapi.interactivepost.render, passing in the appropriate parameters:
// Call the render method when appropriate within your app to display
// the button.
gapi.interactivepost.render('sharePost', options);
When the button renders, the user is presented with the new targeted sharing dialog. When the user shares, those people receive notifications across Google including all of the supported Google products such as their mobile device, GMail, Google Calendar, and so forth.
Again, this demo is here:
http://wheresgus.com/iodemos/demotargetipost/
Making it even better
In the talk on Google+ best practices that Joanna Smith and I gave at I/O this year, we discussed ways that people are going above and beyond the basics in Google+ integrations. I have taken a short pass at ways that people can do this when targeting recipients in interactive posts and here is a subtle example:
function findPeople(){
// only render once - the interactive post will ALSO call the callback
if (!rendered){
gapi.client.plus.people.list(
{
userId : 'me',
collection: 'visible',
orderBy : 'best',
fields : 'items/url,items/id,items/image/url'
}).execute(
function (result){
var people = result.items;
for (var i=0; i < 10 && i < people.length; i++){
contentUrl = people[i].url;
peopleIds += people[i].id;
if (i < 9 && i < people.length-1){
peopleIds += ',';
}
$('#picker').append('<img src="' + people[i].image.url + '"/>');
}
renderPost();
rendered = true;
});
}
}
function renderPost(){
var options = {
contenturl: contentUrl,
clientid: '671005127968.apps.googleusercontent.com',
cookiepolicy: 'single_host_origin',
prefilltext: 'Attack the evil people!!!',
calltoactionlabel: 'ATTACK',
calltoactionurl: 'https://plus.google.com/+GusClass',
recipients: peopleIds
};
// Call the render method when appropriate within your app to display
// the button.
gapi.interactivepost.render('sharePost', options);
}
What I’m doing in this code block is getting the people associated with the currently signed-in user and am filling the recipients with the people visible to the app, ordered by best. This is better because you are taking advantage of the features that Google has made available to you and are using it to grow your app’s audience. What I’m showing in this demo is a mockup for a picker as well as the best people populated into the interactive post. I would recommend that you take it a little further than I did in your integrations and actually let people pick from a few of their friends by clicking on their faces.
Making it amazing
A final improvement you could make would be specific to the information that YOUR site has about your users and their connections. By taking advantage of internal social graphs or interactions of your users, you could create more engaging experiences that would drive more click-throughs for your interactive posts. For example, if you wanted to target interactive posts to people already connected on your site, for things like “contributing to a purchase” on your site, you have this information and Google does not – use this to your advantage and you can drive engagement and growth for your site.
Conclusions
Interactive posts are one of the most significant features that launched with Google+ sign-in. This form of sharing is very powerful and flexible to let your users share with the right people and grow the engaged audience for your apps. Take the time to think about how you can best use this feature for your site and take advantage of order by best in people.list to make the most of the feature.
Yesterday, Joanna and I gave a talk on best practices for the Google+ platform. We discussed just a couple subtle ways that you can make the most of the features that we’ve shipped with the Google+ Sign-In button. In this post, I share a few thoughts on interactive posts, the newest way of sharing on Google+, and a brief look into taking full advantage of targeting posts to specific recipients.
There are two types of sharing available on Google+:
This post is focused on the latter, Interactive posts.
What are Interactive posts?
I like to think of Interactive Posts as a number of things:
- A better way of sharing
- A way of enabling interactions with your online presence
- A way of creating experiences both within and outside the Google+ social stream
- A way of targeting shares to bring people into your apps
Put concisely, the interactive post is a greatly enhanced version of our existing share button that has specifically been updated for the recent release incorporating sign-in for 3rd party web sites that most importantly allows you to generate notifications across Google.
You now can do the following things that you could not before:
- Target posts to specific people
- Prefill text
- Link directly within your apps and sites from the posts in the Google+ stream
- Show different labels within the post other than share
- Include deep links for mobile devices
Now, think about it for a second. The updated functionality of this feature wasn’t chosen at random. Each of these pieces of Interactive Posts is there to let you tailor the content to allow you to make the MOST out of this feature. In this post, I’m going to show you a small way that you can take advantage of just one of these features, the recipients field.
Demo
Enough about what it is, how about a quick demo? Look at a demo of interactive posts. You will notice that the box behaves very similarly to a share. However, the recipients of the post are specified so that each person receives notifications across Google. This is huge because it allows you to create experiences within your application that bring in users to the social experience and allows you to choose the relevant people.
For example, if you have a game where people need to draw a coalition of others to win, you can target your posts to those specific people who you believe the current user can influence.
Code
Let’s talk about interactive posts and how they work:
First, you configure your interactive post by taking a div or other HTML entity, such as the following:
<div id="sharePost"><button>Attack</button></div>
Next, using JavaScript, you configure the post with a content URL, call to action label, and other properties, inherited from the Google+ Sign-in button:
var options = {
contenturl: 'https://blog.gusclass.com',
contentdeeplinkid: '/',
clientid: clientId,
prefilltext: 'I love reading Gus's blog, you should add it to your blog reader.',
calltoactionlabel: 'SUBSCRIBE',
calltoactionurl: 'http://gusclass.com/blog/?feed=rss2',
requestvisibleactions: '',
cookiepolicy: 'http://gusclass.com',
recipients: '109716647623830091721, io2013@gusclass.com'
};
gapi.interactivepost.render(divId, options);
Then, using JavaScript, you call gapi.interactivepost.render, passing in the appropriate parameters:
// Call the render method when appropriate within your app to display
// the button.
gapi.interactivepost.render('sharePost', options);
When the button renders, the user is presented with the new targeted sharing dialog. When the user shares, those people receive notifications across Google including all of the supported Google products such as their mobile device, GMail, Google Calendar, and so forth.
Again, this demo is here:
http://wheresgus.com/iodemos/demotargetipost/
Making it even better
In the talk on Google+ best practices that Joanna Smith and I gave at I/O this year, we discussed ways that people are going above and beyond the basics in Google+ integrations. I have taken a short pass at ways that people can do this when targeting recipients in interactive posts and here is a subtle example:
function findPeople(){
// only render once - the interactive post will ALSO call the callback
if (!rendered){
gapi.client.plus.people.list(
{
userId : 'me',
collection: 'visible',
orderBy : 'best',
fields : 'items/url,items/id,items/image/url'
}).execute(
function (result){
var people = result.items;
for (var i=0; i < 10 && i < people.length; i++){
contentUrl = people[i].url;
peopleIds += people[i].id;
if (i < 9 && i < people.length-1){
peopleIds += ',';
}
$('#picker').append('<img src="' + people[i].image.url + '"/>');
}
renderPost();
rendered = true;
});
}
}
function renderPost(){
var options = {
contenturl: contentUrl,
clientid: '671005127968.apps.googleusercontent.com',
cookiepolicy: 'single_host_origin',
prefilltext: 'Attack the evil people!!!',
calltoactionlabel: 'ATTACK',
calltoactionurl: 'https://plus.google.com/+GusClass',
recipients: peopleIds
};
// Call the render method when appropriate within your app to display
// the button.
gapi.interactivepost.render('sharePost', options);
}
What I’m doing in this code block is getting the people associated with the currently signed-in user and am filling the recipients with the people visible to the app, ordered by best. This is better because you are taking advantage of the features that Google has made available to you and are using it to grow your app’s audience. What I’m showing in this demo is a mockup for a picker as well as the best people populated into the interactive post. I would recommend that you take it a little further than I did in your integrations and actually let people pick from a few of their friends by clicking on their faces.
Making it amazing
A final improvement you could make would be specific to the information that YOUR site has about your users and their connections. By taking advantage of internal social graphs or interactions of your users, you could create more engaging experiences that would drive more click-throughs for your interactive posts. For example, if you wanted to target interactive posts to people already connected on your site, for things like “contributing to a purchase” on your site, you have this information and Google does not – use this to your advantage and you can drive engagement and growth for your site.
Conclusions
Interactive posts are one of the most significant features that launched with Google+ sign-in. This form of sharing is very powerful and flexible to let your users share with the right people and grow the engaged audience for your apps. Take the time to think about how you can best use this feature for your site and take advantage of order by best in people.list to make the most of the feature.
best practices Google+ history Schema.org: best practices google sharing
by gguuss
leave a comment
Passive Sharing: Writing app activities without target URLs
It is a best practice to target your shares to target pages containing Schema content but sometimes you cannot do this. For example, if you are referencing content that is not on your site or if you are writing app activities from mobile devices or Google Chrome extensions, you will need to feed the “schemified” data directly to Google. In this post, I cover how this is done using JavaScript but there are more examples on the Google+ developers site that cover writing app activities using Android without a target URL and writing app activities in other programming languages.
Demo
The following page demos writing app activities without target URIs. Writing app activities like this is appropriate in various cases such as mobile-only scenarios and chrome extensions:
Google+ Platform Demo: Writing moments without target URIs
The key pattern you will notice is as follows:
writeMoment: function(){
var payload = {
"target": {
"id" : "replacewithuniqueidforaddtarget",
"image" : "http:\/\/www.google.com\/s2\/static\/images\/GoogleyEyes.png",
"type" : "http:\/\/schema.org\/CreativeWork",
"description" : "The description for the activity",
"name":"An example of AddActivity"
},
"type":"http:\/\/schemas.google.com\/AddActivity",
"startDate": "2012-10-31T23:59:59.999Z"
};
var args = {
'path': '/plus/v1/people/me/moments/vault',
'method': 'POST',
'body': JSON.stringify(payload),
'callback': function(response) {
console.log(response);
}
};
gapi.client.request(args);
What you will notice is the payload ‘target’ field is JSONified schema markup. Conventionally, this is determined by microdata on the page, but again, sometimes you want to write app activities that don’t have corresponding web pages – the best examples I can think of are when your app is a Chrome extension or Mobile-only. The following additional examples highlight a few different types of activities that you might want to write.
Add Activity
writeAddActivity: function(url){
var payload = {
"type":"http:\/\/schemas.google.com\/AddActivity",
"startDate": "2012-10-31T23:59:59.999Z"
};
if (url != undefined){
payload.target = {
'url' : 'https://developers.google.com/+/plugins/snippet/examples/thing'
};
}else{
payload.target = {
"id" : "replacewithuniqueidforaddtarget",
"image" : "http:\/\/www.google.com\/s2\/static\/images\/GoogleyEyes.png",
"type" : "http:\/\/schema.org\/CreativeWork",
"description" : "The description for the activity",
"name":"An example of AddActivity"
};
}
this.writeAppActivity(payload);
},
writeAppActivity: function(payload){
gapi.client.plus.moments.insert(
{ 'userId' : 'me',
'collection' : 'vault',
'resource' : payload
}).execute(function(result){
console.log(result);
});
}
Listen Activity
writeListenActivity: function(url){
var payload = {
"type": "http://schemas.google.com/ListenActivity",
}
if (url != undefined){
payload.target = { 'url' : url };
}else{
payload.target = {
"type": "http:\/\/schema.org\/MusicRecording",
"id": "uniqueidformusictarget",
"description": "A song about missing one's family members fighting in the American Civil War",
"image": "https:\/\/developers.google.com\/+\/plugins\/snippet\/examples\/song.png",
"name": "When Johnny Comes Marching Home"
};
}
this.writeAppActivity(payload);
Final notes
When you are writing app activities, make sure that you target the BEST app activity type. What this usually means is avoiding the Add activity if possible. If you can change the target schema for your activities to include more relevant information, do it. If you can change the way that your app works to target a new type of activity, do it! Again, I want to reiterate that unless you have to, write your app activities to Google using Schema instead of directly pumping Schemified JSON to Google.
Watch the Google+ sessions at IO to learn about sharing and the latest features from the Google+ team.
It is a best practice to target your shares to target pages containing Schema content but sometimes you cannot do this. For example, if you are referencing content that is not on your site or if you are writing app activities from mobile devices or Google Chrome extensions, you will need to feed the “schemified” data directly to Google. In this post, I cover how this is done using JavaScript but there are more examples on the Google+ developers site that cover writing app activities using Android without a target URL and writing app activities in other programming languages.
Demo
The following page demos writing app activities without target URIs. Writing app activities like this is appropriate in various cases such as mobile-only scenarios and chrome extensions:
Google+ Platform Demo: Writing moments without target URIs
The key pattern you will notice is as follows:
writeMoment: function(){
var payload = {
"target": {
"id" : "replacewithuniqueidforaddtarget",
"image" : "http:\/\/www.google.com\/s2\/static\/images\/GoogleyEyes.png",
"type" : "http:\/\/schema.org\/CreativeWork",
"description" : "The description for the activity",
"name":"An example of AddActivity"
},
"type":"http:\/\/schemas.google.com\/AddActivity",
"startDate": "2012-10-31T23:59:59.999Z"
};
var args = {
'path': '/plus/v1/people/me/moments/vault',
'method': 'POST',
'body': JSON.stringify(payload),
'callback': function(response) {
console.log(response);
}
};
gapi.client.request(args);
What you will notice is the payload ‘target’ field is JSONified schema markup. Conventionally, this is determined by microdata on the page, but again, sometimes you want to write app activities that don’t have corresponding web pages – the best examples I can think of are when your app is a Chrome extension or Mobile-only. The following additional examples highlight a few different types of activities that you might want to write.
Add Activity
writeAddActivity: function(url){
var payload = {
"type":"http:\/\/schemas.google.com\/AddActivity",
"startDate": "2012-10-31T23:59:59.999Z"
};
if (url != undefined){
payload.target = {
'url' : 'https://developers.google.com/+/plugins/snippet/examples/thing'
};
}else{
payload.target = {
"id" : "replacewithuniqueidforaddtarget",
"image" : "http:\/\/www.google.com\/s2\/static\/images\/GoogleyEyes.png",
"type" : "http:\/\/schema.org\/CreativeWork",
"description" : "The description for the activity",
"name":"An example of AddActivity"
};
}
this.writeAppActivity(payload);
},
writeAppActivity: function(payload){
gapi.client.plus.moments.insert(
{ 'userId' : 'me',
'collection' : 'vault',
'resource' : payload
}).execute(function(result){
console.log(result);
});
}
Listen Activity
writeListenActivity: function(url){
var payload = {
"type": "http://schemas.google.com/ListenActivity",
}
if (url != undefined){
payload.target = { 'url' : url };
}else{
payload.target = {
"type": "http:\/\/schema.org\/MusicRecording",
"id": "uniqueidformusictarget",
"description": "A song about missing one's family members fighting in the American Civil War",
"image": "https:\/\/developers.google.com\/+\/plugins\/snippet\/examples\/song.png",
"name": "When Johnny Comes Marching Home"
};
}
this.writeAppActivity(payload);
Final notes
When you are writing app activities, make sure that you target the BEST app activity type. What this usually means is avoiding the Add activity if possible. If you can change the target schema for your activities to include more relevant information, do it. If you can change the way that your app works to target a new type of activity, do it! Again, I want to reiterate that unless you have to, write your app activities to Google using Schema instead of directly pumping Schemified JSON to Google.
Watch the Google+ sessions at IO to learn about sharing and the latest features from the Google+ team.
best practices Google+ programming: developers documentation google programming
by gguuss
leave a comment
Making the most of the Google+ developer site
The curse of knowledge – knowing something and then not realizing that you have this knowledge – can make it difficult to remember the first time that you started learning about something. A great example of this is the Google developer documentation. Virtually every set of developer documentation that I have used and contributed to has been unique to the site or project that I have been working on and Google+ is no exception. This is not by design, writers spend hours, weeks, even months planning and mapping out how the documentation will be organized based on how the user will learn from it and how they will find the things that they are looking for. Documentation organization and information architecture are extremely difficult and no particular solution is best.
In this blog post I’ll try and summarize a few key points of how you can use the Google+ developer documentation that I have learned from using it constantly.
A high level overview
In case you have missed it, the Google+ Developer site is at https://developers.google.com/+.
The most important part of being a power user of the Google+ documentation is to understand the navigation bar on the left and what the blocks of content represent. The following diagram summarizes each section:

- At the top, you have the most effective resources for getting started. These cover the “what is it” and “how do I see it work” topics. I’ll refer to this as the getting started section.
- Next, you have a breakdown of Google+ by feature and platform. I’ll just call this section features and platforms.
- Next, you have a breakdown of the developer resources that are quick references for the top resources that developers are looking for. I like to think of this as “get the stuff” but really it’s better summarized as Top Resources.
- Finally, you have the community updates and other broader information for Google+ developers. This is the external/community resource section for Google+.
Given that breakdown, you can jump into each section with some sort of expectations and understanding for what you will encounter. I’ll now dive deeper into each section.
Getting Started
This section covers the first resources that you will want to get to know and experiment with the product. This is breadth content that is not specific to a feature but is generalized to a high level view of the Google+ platform. The key content here is:
- A quick platform overview for Google+.
- Getting started samples. We call these the quickstarts because they should get you going in around 10 minutes. These are broken down by platform / programming language (Android, iOS, Python, etc).
- The Photohunt sample. This is our showcase demo that brings together the features into something interesting and also demonstrates best practices for end-to-end applications built on Google+.
- The API reference that lets you understand the full capabilities of the Google+ API platform.
When you dive into this content, you are typically looking to understand the platform overall and quickly get going. When you reach this content, it’s assumed that you want to experiment and explore by diving right into using the platform.
Features and Platforms
This section covers each part of the platform atomically and is the most visually focused content on the developer site. You can look at all of the available features to understand them as well as how to use them. There is a ton of content in this section that is essential to understanding and using the platform. You can also learn about why each feature and platform is useful for you. It’s broken down to cover:
- Google+ features breakdown by capabilities. Here you can learn all about the core platform features of Google+. Sign-in, interactive posts, app activities, over-the-air installs, and so on.
- iOS and Android mobile platforms
- Hangouts
You can think of this section as a palette from which you can paint products. Each of these components is available and summarized for you to understand how it can work for you. Each of the sections contains code and examples ready to copy and paste. When you’re looking for a solution provided by the product / Google+ platform, this is where you go. When you want to see a feature and then jump in to understand how to use it, you can find everything right here. To discover the relevant content you can either dive right in to the platform that you are using, click through the visual content to the highlighted buttons on the top of the page.
Top resources
This section covers much of the “Get it now” contents and resources that can be shared with external folks you are working with.
From this section you can get:
- The API and SDK resources in the Download section
- The top best practices content
- The Branding Guidelines for designing custom UI that conforms to Google+ branding
If you are looking for resources to point agencies to, these are the important ones. If you are looking to get the components for building apps, this is also where you should look.
External / Community for Google+
These links cover the topics for being good citizens in the Google+ ecosystem and getting involved with the exciting things that are going on with Google+. From this section, you will find resources to:
- External and First party Google+ communities. We put our Google+ Developers Live videos here along with links to our Google+ community for Google+ Developers, our Google+ Developers page, our tag on Stack Overflow, and our official Google+ GitHub page.
- The Google+ platform issue tracker for bug reporting and feature requests
- Release notes for enthusiasts and folks who need to track changes as they are released.
- Developer policies and terms of service so that you can be a good citizen in the Google+ ecosystem.
When you are looking to maintain a deep understanding of Google+ and are getting to a more mature state with Google+ development, these sections are essential. You can connect with other people who are developing with Google+, can find resolutions to specific problems, and can learn the intricacies of how we want developers to use Google+.
Thoughts and Conclusions
I know that most people reach our content through search. However, it can be really useful to get acquainted with the layout of our developer content so that you can also explore and discover the relevant content for your Google+ integration. The Google+ content team of writers has put immense amounts of effort into making our site as useful as possible for developers, take advantage of all the work they’ve put in! I’ll add a few more summaries to help you understand a few key things that you should look at and will reference over the course of your career as a Google+ developer.
Requirements for developing with Google+:
API Overviews:
- The full Google+ API Reference
- The Hangouts API reference
- The iOS API reference
- The Android API reference
Code:
Please contribute to making the site better through the issue tracker and by telling the Google+ developer team what you think on the Google+ developers page. The feedback that goes through these channels really and truly shapes the future of the platform and the Google+ developer content.
A final note, all of the latest Google+ features are being covered at Google IO this week. Follow the Google+ track either at the conference, at IO extended meetups, or online to learn all of the great new opportunities that are possible with Google+.
The curse of knowledge – knowing something and then not realizing that you have this knowledge – can make it difficult to remember the first time that you started learning about something. A great example of this is the Google developer documentation. Virtually every set of developer documentation that I have used and contributed to has been unique to the site or project that I have been working on and Google+ is no exception. This is not by design, writers spend hours, weeks, even months planning and mapping out how the documentation will be organized based on how the user will learn from it and how they will find the things that they are looking for. Documentation organization and information architecture are extremely difficult and no particular solution is best.
In this blog post I’ll try and summarize a few key points of how you can use the Google+ developer documentation that I have learned from using it constantly.
A high level overview
In case you have missed it, the Google+ Developer site is at https://developers.google.com/+.
The most important part of being a power user of the Google+ documentation is to understand the navigation bar on the left and what the blocks of content represent. The following diagram summarizes each section:

- At the top, you have the most effective resources for getting started. These cover the “what is it” and “how do I see it work” topics. I’ll refer to this as the getting started section.
- Next, you have a breakdown of Google+ by feature and platform. I’ll just call this section features and platforms.
- Next, you have a breakdown of the developer resources that are quick references for the top resources that developers are looking for. I like to think of this as “get the stuff” but really it’s better summarized as Top Resources.
- Finally, you have the community updates and other broader information for Google+ developers. This is the external/community resource section for Google+.
Given that breakdown, you can jump into each section with some sort of expectations and understanding for what you will encounter. I’ll now dive deeper into each section.
Getting Started
This section covers the first resources that you will want to get to know and experiment with the product. This is breadth content that is not specific to a feature but is generalized to a high level view of the Google+ platform. The key content here is:
- A quick platform overview for Google+.
- Getting started samples. We call these the quickstarts because they should get you going in around 10 minutes. These are broken down by platform / programming language (Android, iOS, Python, etc).
- The Photohunt sample. This is our showcase demo that brings together the features into something interesting and also demonstrates best practices for end-to-end applications built on Google+.
- The API reference that lets you understand the full capabilities of the Google+ API platform.
When you dive into this content, you are typically looking to understand the platform overall and quickly get going. When you reach this content, it’s assumed that you want to experiment and explore by diving right into using the platform.
Features and Platforms
This section covers each part of the platform atomically and is the most visually focused content on the developer site. You can look at all of the available features to understand them as well as how to use them. There is a ton of content in this section that is essential to understanding and using the platform. You can also learn about why each feature and platform is useful for you. It’s broken down to cover:
- Google+ features breakdown by capabilities. Here you can learn all about the core platform features of Google+. Sign-in, interactive posts, app activities, over-the-air installs, and so on.
- iOS and Android mobile platforms
- Hangouts
You can think of this section as a palette from which you can paint products. Each of these components is available and summarized for you to understand how it can work for you. Each of the sections contains code and examples ready to copy and paste. When you’re looking for a solution provided by the product / Google+ platform, this is where you go. When you want to see a feature and then jump in to understand how to use it, you can find everything right here. To discover the relevant content you can either dive right in to the platform that you are using, click through the visual content to the highlighted buttons on the top of the page.
Top resources
This section covers much of the “Get it now” contents and resources that can be shared with external folks you are working with.
From this section you can get:
- The API and SDK resources in the Download section
- The top best practices content
- The Branding Guidelines for designing custom UI that conforms to Google+ branding
If you are looking for resources to point agencies to, these are the important ones. If you are looking to get the components for building apps, this is also where you should look.
External / Community for Google+
These links cover the topics for being good citizens in the Google+ ecosystem and getting involved with the exciting things that are going on with Google+. From this section, you will find resources to:
- External and First party Google+ communities. We put our Google+ Developers Live videos here along with links to our Google+ community for Google+ Developers, our Google+ Developers page, our tag on Stack Overflow, and our official Google+ GitHub page.
- The Google+ platform issue tracker for bug reporting and feature requests
- Release notes for enthusiasts and folks who need to track changes as they are released.
- Developer policies and terms of service so that you can be a good citizen in the Google+ ecosystem.
When you are looking to maintain a deep understanding of Google+ and are getting to a more mature state with Google+ development, these sections are essential. You can connect with other people who are developing with Google+, can find resolutions to specific problems, and can learn the intricacies of how we want developers to use Google+.
Thoughts and Conclusions
I know that most people reach our content through search. However, it can be really useful to get acquainted with the layout of our developer content so that you can also explore and discover the relevant content for your Google+ integration. The Google+ content team of writers has put immense amounts of effort into making our site as useful as possible for developers, take advantage of all the work they’ve put in! I’ll add a few more summaries to help you understand a few key things that you should look at and will reference over the course of your career as a Google+ developer.
Requirements for developing with Google+:
API Overviews:
- The full Google+ API Reference
- The Hangouts API reference
- The iOS API reference
- The Android API reference
Code:
Please contribute to making the site better through the issue tracker and by telling the Google+ developer team what you think on the Google+ developers page. The feedback that goes through these channels really and truly shapes the future of the platform and the Google+ developer content.
A final note, all of the latest Google+ features are being covered at Google IO this week. Follow the Google+ track either at the conference, at IO extended meetups, or online to learn all of the great new opportunities that are possible with Google+.
Manipulating sites using the DOM editor in Chrome
Ever visit a site and there’s just something about how it looks that you don’t like? Ever want to modify elements of your site in real-time without having to refresh the page? You can do all of this and more the DOM editor in Chrome.
An example
I have been using bloglines as my cloud RSS reader since, well, I don’t know, the DAWN OF THE INTERNET. Anyways, it recently introduced some rendering errors when I’m looking at some feeds. The following image highlights how bad it can be:
Because these rendering errors are a blight on my RSS life, I have developed a habit of just removing the footer all together. First, I right click on the footer and select inspect element:
Next, I find the node in the HTML sources (footer):
Finally, I delete it! The results are a much more aesthetically pleasing experience:
The thing is, I don’t like having to go back to the DOM inspector every time. Why not use Tampermonkey to do this for me!
Tampermonkey scripting
Tampermonkey lets you easily manipulate the DOM. You can use selectors to look at elements and automatically manipulate them. For example, let’s say that you want to find the element with the ID “footer” and remove it. The following script will set it to null, effectively eliminating the object from the rendered DOM:
// ==UserScript==
// @name Bloglines footer remover
// @namespace http://gusclass.com/blog
// @version 0.1
// @description enter something useful
// @match http://*bloglines.com/*
// @copyright 2013+, Gus Class
// ==/UserScript==
document.getElementById("footer").style.display = 'none';
Note, of great importance, the @match section (in comment) prevents my script from removing footers from sites across the internet.
Now that I have set up the script, I can enable/disable it from the Chrome extensions bar as seen in the following screenshot:
When the icon for the “My fancy new …” script is set to enabled, indicated by green, the extension will manipulate the rendered DOM to snip away the footer, generating the desired UI for Bloglines for me.
Conclusions
The web is experimental and open. If you want it to look different in your browser, the potential is there and you just need to untap it. Play around with the available extensions to see how you can rewrite the web to be better for you!
Ever visit a site and there’s just something about how it looks that you don’t like? Ever want to modify elements of your site in real-time without having to refresh the page? You can do all of this and more the DOM editor in Chrome.
An example
I have been using bloglines as my cloud RSS reader since, well, I don’t know, the DAWN OF THE INTERNET. Anyways, it recently introduced some rendering errors when I’m looking at some feeds. The following image highlights how bad it can be:
Because these rendering errors are a blight on my RSS life, I have developed a habit of just removing the footer all together. First, I right click on the footer and select inspect element:
Next, I find the node in the HTML sources (footer):
Finally, I delete it! The results are a much more aesthetically pleasing experience:
The thing is, I don’t like having to go back to the DOM inspector every time. Why not use Tampermonkey to do this for me!
Tampermonkey scripting
Tampermonkey lets you easily manipulate the DOM. You can use selectors to look at elements and automatically manipulate them. For example, let’s say that you want to find the element with the ID “footer” and remove it. The following script will set it to null, effectively eliminating the object from the rendered DOM:
// ==UserScript==
// @name Bloglines footer remover
// @namespace http://gusclass.com/blog
// @version 0.1
// @description enter something useful
// @match http://*bloglines.com/*
// @copyright 2013+, Gus Class
// ==/UserScript==
document.getElementById("footer").style.display = 'none';
Note, of great importance, the @match section (in comment) prevents my script from removing footers from sites across the internet.
Now that I have set up the script, I can enable/disable it from the Chrome extensions bar as seen in the following screenshot:
When the icon for the “My fancy new …” script is set to enabled, indicated by green, the extension will manipulate the rendered DOM to snip away the footer, generating the desired UI for Bloglines for me.
Conclusions
The web is experimental and open. If you want it to look different in your browser, the potential is there and you just need to untap it. Play around with the available extensions to see how you can rewrite the web to be better for you!
Even more common errors with Sign-in
After a few months of debugging other people’s code and seeing real-world integrations, I have encountered some strange issues that have baffled me but that had frustratingly simple solutions! Here are a few more that we hadn’t yet encountered when we had launched but that came up over our integrations in bootcamps with Google+ partners. If you encounter other errors, please let me know on Google+!
Getting 401 errors but my sign-in button is including the right visible actions, what gives?
Here is an interesting situation… What could be happening is that users are connecting to your application with the interactive post button and the callback is different from the sign-in button. What you need to take to heart here is that the interactive post button IS a sign-in button. Match your scopes, match requestvisibleactions, and know that the callback will be triggered when the interactivepost button loads. Here’s an example of a Sign-in button and interactive post button with matching scopes:
<html>
<body>
<script type="text/javascript">
function renderPost(){
var options = {
scope: 'https://www.googleapis.com/auth/plus.login',
requestvisibleactions: 'http://schemas.google.com/AddActivity',
contenturl: 'https://plus.google.com/+GusClass',
clientid: '671005127968.apps.googleusercontent.com',
cookiepolicy: 'single_host_origin',
prefilltext: 'Attack the evil Gus!!!',
calltoactionlabel: 'ATTACK',
calltoactionurl: 'https://plus.google.com/+GusClass',
recipients: '109716647623830091721'
};
// Call the render method when appropriate within your app to display
// the button.
gapi.interactivepost.render('sharePost', options);
}
</script>
<p>
<span id="signinButton">
<span
class="g-signin"
data-callback="signinCallback"
data-clientid="671005127968.apps.googleusercontent.com"
data-cookiepolicy="single_host_origin"
data-requestvisibleactions="http://schemas.google.com/AddActivity"
data-scope="https://www.googleapis.com/auth/plus.login">
</span>
</span>
<div id="sharePost"><button>Attack</button></div>
</p>
<script type="text/javascript">
(function() {
var po = document.createElement('script'); po.type = 'text/javascript'; po.async = true;
po.src = 'http://apis.google.com/js/client:plusone.js?onload=renderPost';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(po, s);
})();
</script>
</body>
</html>
Getting 400 errors when writing app activities
Another interesting situation
This happens when you are writing app activities and you have added extra payloads or are missing requisite data. If, for example, you have an App activity that looks like the following target-less payload in JavaScript:
var payload = {
"type":"http:\/\/schemas.google.com\/AddActivity",
"startDate": "2012-10-31T23:59:59.999Z"
};
payload.target = {
"id" : "replacewithuniqueidforaddtarget",
"theGusField" : "Just doin Gus stuff",
"image" : "http:\/\/www.google.com\/s2\/static\/images\/GoogleyEyes.png",
"type" : "http:\/\/schema.org\/CreativeWork",
"description" : "The description for the activity",
"name":"An example of AddActivity"
};
helper.writeAppActivity(payload);
“theGusField” will cause the write to return a 400. Remove the extra field and you will successfully be returned an object. This is just one more reason that you should be using the targeturl field and taking advantage of Schema for populating your share content.
Getting 403 errors on an API call
This means that you have not enabled a particular service for that API call in your APIs console. Let’s say that you’re trying to make an API call to Google+ but your client hasn’t fully been configured yet, you would get this error.
To fix it…
Go to the Google APIs console and select the services tab.
Ensure that you have enabled Google+ (or whatever API you are accessing…) and these errors will go away!
For more information
After a few months of debugging other people’s code and seeing real-world integrations, I have encountered some strange issues that have baffled me but that had frustratingly simple solutions! Here are a few more that we hadn’t yet encountered when we had launched but that came up over our integrations in bootcamps with Google+ partners. If you encounter other errors, please let me know on Google+!
Getting 401 errors but my sign-in button is including the right visible actions, what gives?
Here is an interesting situation… What could be happening is that users are connecting to your application with the interactive post button and the callback is different from the sign-in button. What you need to take to heart here is that the interactive post button IS a sign-in button. Match your scopes, match requestvisibleactions, and know that the callback will be triggered when the interactivepost button loads. Here’s an example of a Sign-in button and interactive post button with matching scopes:
<html>
<body>
<script type="text/javascript">
function renderPost(){
var options = {
scope: 'https://www.googleapis.com/auth/plus.login',
requestvisibleactions: 'http://schemas.google.com/AddActivity',
contenturl: 'https://plus.google.com/+GusClass',
clientid: '671005127968.apps.googleusercontent.com',
cookiepolicy: 'single_host_origin',
prefilltext: 'Attack the evil Gus!!!',
calltoactionlabel: 'ATTACK',
calltoactionurl: 'https://plus.google.com/+GusClass',
recipients: '109716647623830091721'
};
// Call the render method when appropriate within your app to display
// the button.
gapi.interactivepost.render('sharePost', options);
}
</script>
<p>
<span id="signinButton">
<span
class="g-signin"
data-callback="signinCallback"
data-clientid="671005127968.apps.googleusercontent.com"
data-cookiepolicy="single_host_origin"
data-requestvisibleactions="http://schemas.google.com/AddActivity"
data-scope="https://www.googleapis.com/auth/plus.login">
</span>
</span>
<div id="sharePost"><button>Attack</button></div>
</p>
<script type="text/javascript">
(function() {
var po = document.createElement('script'); po.type = 'text/javascript'; po.async = true;
po.src = 'http://apis.google.com/js/client:plusone.js?onload=renderPost';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(po, s);
})();
</script>
</body>
</html>
Getting 400 errors when writing app activities
Another interesting situation
This happens when you are writing app activities and you have added extra payloads or are missing requisite data. If, for example, you have an App activity that looks like the following target-less payload in JavaScript:
var payload = {
"type":"http:\/\/schemas.google.com\/AddActivity",
"startDate": "2012-10-31T23:59:59.999Z"
};
payload.target = {
"id" : "replacewithuniqueidforaddtarget",
"theGusField" : "Just doin Gus stuff",
"image" : "http:\/\/www.google.com\/s2\/static\/images\/GoogleyEyes.png",
"type" : "http:\/\/schema.org\/CreativeWork",
"description" : "The description for the activity",
"name":"An example of AddActivity"
};
helper.writeAppActivity(payload);
“theGusField” will cause the write to return a 400. Remove the extra field and you will successfully be returned an object. This is just one more reason that you should be using the targeturl field and taking advantage of Schema for populating your share content.
Getting 403 errors on an API call
This means that you have not enabled a particular service for that API call in your APIs console. Let’s say that you’re trying to make an API call to Google+ but your client hasn’t fully been configured yet, you would get this error.
To fix it…
Go to the Google APIs console and select the services tab.
Ensure that you have enabled Google+ (or whatever API you are accessing…) and these errors will go away!
For more information
best practices patterns Plus programming: best practices javascript programming
by gguuss
leave a comment
Google API clients: Why you should use them and how
Some of our older code examples, including many that I have authored on my blog have been using raw query execution against the Google APIs. This is undesirable and is absolutely not a best practice because you will not benefit from the convenience and reliability that comes with using methods on the API.
The beauty of the Google APIs is that they use a discovery service that allows Google and third parties to generate updated packages on the fly for developers to consume in their target languages. What this means is that as the APIs change, the client libraries will automatically be updated to support these changes. Furthermore, these client libraries are owned and tested by Google making them much more stable and reliable than using raw access to the API endpoints.
In this post, I’ll show you first a few bad examples for queries and will show you better ways of accessing Google’s APIs.
The first culprit: client request execution
The Google API clients support performing raw execution against the Google APIs using appropriate network stacks for that particular client. This is great design because client queries can share the same execution code and benefit from improvements in caching, performance, reliability, and so forth. Additionally, the API will manage authorization and will manage your bearer token for you. However, accessing the APIs in this manner requires you to parse and manage JSON objects which can introduce bugs and support issues for you, the developer.
For example, in this post, I run the following code (do not do this):
var payload = {
"target": {
"id" : "replacewithuniqueidforaddtarget",
"image" : "http://www.google.com/s2/static/images/GoogleyEyes.png",
"type" : "http://schema.org/CreativeWork",
"description" : "The description for the activity",
"name":"An example of AddActivity"
},
"type":"http://schemas.google.com/AddActivity",
"startDate": "2012-10-31T23:59:59.999Z"
};
var args = {
'path': '/plus/v1/people/me/moments/vault',
'method': 'POST',
'body': JSON.stringify(payload),
'callback': function(response) {
console.log(response);
}
};
gapi.client.request(args);
What this code is doing is directly POSTing JSON data to the API endpoint. This is undesirable because there are a number of ways that things can go wrong and you must write loads of unnecessary and difficult to read code. In addition, you have to stringify the API call when you make it and also parse the response from the query.
Next culprit: XHR / RAW HTTP requests
This is actually worse than the first culprit: rolling your own client using the XHR object in JavaScript. The following example, from code I wrote roughly a year ago, illustrates what not to do (DO NOT DO THIS):
// globals used for auth, showing debugging
var debug = true;
var key = "AIzaSyC63QTL6Brw_4IQFK6uKRQDj-KmGPKMwnA";
function handleRequestIssue(request){
// For now, just can cry about it..
console.log("khhhhaaaaann!!!, status:" + request.status + " / response:" + request.responseText);
}
// Gets the activities for a profile
function getActivities(profileID){
var activities = null;
var URL = "https://www.googleapis.com/plus/v1/people/" + profileID + "/activities/public?alt=json&key=" + key;
var request = new XMLHttpRequest();
request.open('GET', URL, false);
request.send(); // because of "false" above, will block until the request is done
// and status is available. Not recommended, however it works for simple cases.
if (request.status === 200) {
if (debug) console.log("retrieved activities nn");
if (debug) console.log("rawnn" + request.responseText);
activities = jQuery.parseJSON(request.responseText).items;
console.log("Discovered " + activities.length + " activities");
console.log("raw:" + request.responseText);
}else{
handleRequestIssue(request);
}
return activities;
}
function getCommentsForActivity(activityID){
var comments = "";
var URL = "https://www.googleapis.com/plus/v1/activities/" + activityID + "/comments?alt=json&key=" + key;
var request = new XMLHttpRequest();
request.open('GET', URL, false);
request.send(); // because of "false" above, will block until the request is done
// and status is available. Not recommended, however it works for simple cases.
if (request.status === 200) {
if (debug) console.log(request.responseText);
comments = jQuery.parseJSON(request.responseText).items;
if (debug){
for (comment in comments){
console.log(comment);
}
}
}else{
handleRequestIssue(request);
}
return comments;
}
function manualTrigger(){
var activities = getActivities("109716647623830091721");
}
As you can see, this code is even less readable than the first set of code! I’m even adding an ugly and blatantly terrible error handler that lacks remediation for the response issues. The code is super fragile: changes at multiple points in the process will introduce bugs in my demo. Do not access the APIs this way.
Doing it RIGHT: The Google API Clients
Here is the best way to do this: using the Google API clients and accessing the functionality and data through the API client. The following JavaScript example lists comments from the current user’s stream and can be seen here: Demo: listing comments (DO IT THIS WAY):
<html>
<script>
var output;
function signinCallback(result){
console.log(result);
if (result.access_token != null){
// success
document.getElementById('signinButton').style.display = 'none';
output = document.getElementById('output');
}
// Load the client library and call getActivities once it's loaded
gapi.client.load('plus', 'v1', getActivities);
}
// Gets the activities for a profile using the client library
function getActivities(){
var activities = null;
gapi.client.plus.activities.list({'userId' : 'me'}).execute(
function(activities){
console.log(activities);
for (var index = 0; index < activities.items.length; index++){
output.value += 'nn-------n' + JSON.stringify(activities.items[index]);
output.innerText = output.innerText + JSON.stringify;
getCommentsForActivity(activities.items[index].id);
}
});
}
function getCommentsForActivity(activityID){
gapi.client.plus.comments.list(
{'activityId' : activityID}).
execute(
function(comment){
output.value += 'n' + JSON.stringify(comment);
console.log(comment);
});
}
</script>
<body>
<span id="signinButton">
<span
class="g-signin"
data-callback="signinCallback"
data-clientid="1065047579848.apps.googleusercontent.com"
data-cookiepolicy="single_host_origin"
data-scope="https://www.googleapis.com/auth/plus.login">
</span>
</span>
<textarea cols="80" rows="40" id="output">
</textarea>
<!-- Place this asynchronous JavaScript just before your </body> tag -->
<script type="text/javascript">
(function() {
var po = document.createElement('script'); po.type = 'text/javascript'; po.async = true;
po.src = 'https://apis.google.com/js/client:plusone.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(po, s);
})();
</script>
</body>
</html>
See how much better it is! This code is even doing MORE than the other code in fewer lines. You get nice objects and methods for all of the operations performed on Google+ and the code will be updated from Google if anything ever changes. By accessing the API in this manner, you will be getting all of the client library updates by loading the client library from Google and will get a reliable means of accessing the API functionality.
The Google+ quickstarts show how to use the client libraries in a number of languages, this is a great way to get started with understanding the relevant patterns for your language of choice. Also, this is a great way to learn how to perform the operations in a language you understand less because the samples are virtually identical in all languages.
Conclusion
Although it may seem obvious now, always make sure that you use the Google API clients when programming against the Google APIs. There is a client for virtually every language out there to make your code more maintainable, stable, performant, and readable. The API clients support much more than I covered here that makes your life easier. Many other great examples of client execution can be found on Ian Barber’s blog. For example, the following article shows how to use the API client to batch API calls:
The generic Google client libraries that use the discovery service can be found at:
You can also download the Google+ API clients from the Google+ developer pages that are targeted to Google+.
For certain languages such as C# and Java, the client libraries must be generated, so occasionally it may be a good idea to update your clients if you’re using these languages. Have fun accessing the APIs, and be sure to do it the right way, using the freely available clients!
Some of our older code examples, including many that I have authored on my blog have been using raw query execution against the Google APIs. This is undesirable and is absolutely not a best practice because you will not benefit from the convenience and reliability that comes with using methods on the API.
The beauty of the Google APIs is that they use a discovery service that allows Google and third parties to generate updated packages on the fly for developers to consume in their target languages. What this means is that as the APIs change, the client libraries will automatically be updated to support these changes. Furthermore, these client libraries are owned and tested by Google making them much more stable and reliable than using raw access to the API endpoints.
In this post, I’ll show you first a few bad examples for queries and will show you better ways of accessing Google’s APIs.
The first culprit: client request execution
The Google API clients support performing raw execution against the Google APIs using appropriate network stacks for that particular client. This is great design because client queries can share the same execution code and benefit from improvements in caching, performance, reliability, and so forth. Additionally, the API will manage authorization and will manage your bearer token for you. However, accessing the APIs in this manner requires you to parse and manage JSON objects which can introduce bugs and support issues for you, the developer.
For example, in this post, I run the following code (do not do this):
var payload = {
"target": {
"id" : "replacewithuniqueidforaddtarget",
"image" : "http://www.google.com/s2/static/images/GoogleyEyes.png",
"type" : "http://schema.org/CreativeWork",
"description" : "The description for the activity",
"name":"An example of AddActivity"
},
"type":"http://schemas.google.com/AddActivity",
"startDate": "2012-10-31T23:59:59.999Z"
};
var args = {
'path': '/plus/v1/people/me/moments/vault',
'method': 'POST',
'body': JSON.stringify(payload),
'callback': function(response) {
console.log(response);
}
};
gapi.client.request(args);
What this code is doing is directly POSTing JSON data to the API endpoint. This is undesirable because there are a number of ways that things can go wrong and you must write loads of unnecessary and difficult to read code. In addition, you have to stringify the API call when you make it and also parse the response from the query.
Next culprit: XHR / RAW HTTP requests
This is actually worse than the first culprit: rolling your own client using the XHR object in JavaScript. The following example, from code I wrote roughly a year ago, illustrates what not to do (DO NOT DO THIS):
// globals used for auth, showing debugging
var debug = true;
var key = "AIzaSyC63QTL6Brw_4IQFK6uKRQDj-KmGPKMwnA";
function handleRequestIssue(request){
// For now, just can cry about it..
console.log("khhhhaaaaann!!!, status:" + request.status + " / response:" + request.responseText);
}
// Gets the activities for a profile
function getActivities(profileID){
var activities = null;
var URL = "https://www.googleapis.com/plus/v1/people/" + profileID + "/activities/public?alt=json&key=" + key;
var request = new XMLHttpRequest();
request.open('GET', URL, false);
request.send(); // because of "false" above, will block until the request is done
// and status is available. Not recommended, however it works for simple cases.
if (request.status === 200) {
if (debug) console.log("retrieved activities nn");
if (debug) console.log("rawnn" + request.responseText);
activities = jQuery.parseJSON(request.responseText).items;
console.log("Discovered " + activities.length + " activities");
console.log("raw:" + request.responseText);
}else{
handleRequestIssue(request);
}
return activities;
}
function getCommentsForActivity(activityID){
var comments = "";
var URL = "https://www.googleapis.com/plus/v1/activities/" + activityID + "/comments?alt=json&key=" + key;
var request = new XMLHttpRequest();
request.open('GET', URL, false);
request.send(); // because of "false" above, will block until the request is done
// and status is available. Not recommended, however it works for simple cases.
if (request.status === 200) {
if (debug) console.log(request.responseText);
comments = jQuery.parseJSON(request.responseText).items;
if (debug){
for (comment in comments){
console.log(comment);
}
}
}else{
handleRequestIssue(request);
}
return comments;
}
function manualTrigger(){
var activities = getActivities("109716647623830091721");
}
As you can see, this code is even less readable than the first set of code! I’m even adding an ugly and blatantly terrible error handler that lacks remediation for the response issues. The code is super fragile: changes at multiple points in the process will introduce bugs in my demo. Do not access the APIs this way.
Doing it RIGHT: The Google API Clients
Here is the best way to do this: using the Google API clients and accessing the functionality and data through the API client. The following JavaScript example lists comments from the current user’s stream and can be seen here: Demo: listing comments (DO IT THIS WAY):
<html>
<script>
var output;
function signinCallback(result){
console.log(result);
if (result.access_token != null){
// success
document.getElementById('signinButton').style.display = 'none';
output = document.getElementById('output');
}
// Load the client library and call getActivities once it's loaded
gapi.client.load('plus', 'v1', getActivities);
}
// Gets the activities for a profile using the client library
function getActivities(){
var activities = null;
gapi.client.plus.activities.list({'userId' : 'me'}).execute(
function(activities){
console.log(activities);
for (var index = 0; index < activities.items.length; index++){
output.value += 'nn-------n' + JSON.stringify(activities.items[index]);
output.innerText = output.innerText + JSON.stringify;
getCommentsForActivity(activities.items[index].id);
}
});
}
function getCommentsForActivity(activityID){
gapi.client.plus.comments.list(
{'activityId' : activityID}).
execute(
function(comment){
output.value += 'n' + JSON.stringify(comment);
console.log(comment);
});
}
</script>
<body>
<span id="signinButton">
<span
class="g-signin"
data-callback="signinCallback"
data-clientid="1065047579848.apps.googleusercontent.com"
data-cookiepolicy="single_host_origin"
data-scope="https://www.googleapis.com/auth/plus.login">
</span>
</span>
<textarea cols="80" rows="40" id="output">
</textarea>
<!-- Place this asynchronous JavaScript just before your </body> tag -->
<script type="text/javascript">
(function() {
var po = document.createElement('script'); po.type = 'text/javascript'; po.async = true;
po.src = 'https://apis.google.com/js/client:plusone.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(po, s);
})();
</script>
</body>
</html>
See how much better it is! This code is even doing MORE than the other code in fewer lines. You get nice objects and methods for all of the operations performed on Google+ and the code will be updated from Google if anything ever changes. By accessing the API in this manner, you will be getting all of the client library updates by loading the client library from Google and will get a reliable means of accessing the API functionality.
The Google+ quickstarts show how to use the client libraries in a number of languages, this is a great way to get started with understanding the relevant patterns for your language of choice. Also, this is a great way to learn how to perform the operations in a language you understand less because the samples are virtually identical in all languages.
Conclusion
Although it may seem obvious now, always make sure that you use the Google API clients when programming against the Google APIs. There is a client for virtually every language out there to make your code more maintainable, stable, performant, and readable. The API clients support much more than I covered here that makes your life easier. Many other great examples of client execution can be found on Ian Barber’s blog. For example, the following article shows how to use the API client to batch API calls:
The generic Google client libraries that use the discovery service can be found at:
You can also download the Google+ API clients from the Google+ developer pages that are targeted to Google+.
For certain languages such as C# and Java, the client libraries must be generated, so occasionally it may be a good idea to update your clients if you’re using these languages. Have fun accessing the APIs, and be sure to do it the right way, using the freely available clients!
CSharp productivity programming: C# development I can't believe it's not Visual Studio programming
by gguuss
1 comment
Developing in C# from OS X using MonoDevelop
Before we begin, you can get MonoDevelop from here: MonoDevelop
I have been playing a little bit with MonoDevelop as an alternative to running a VM from OS X for C# development. After spending a little time with it, MonoDevelop really isn’t that bad…
(I can hear the *hissing* from naysayers)
… Really! It’s not Visual Studio, mind you, but it’s still a great IDE. For OS X, the latest versions have pretty awesome coverage of the .NET 4.0 specification and C# 4. Even a good portion of the .NET 4.5 specification is supported and C# 5 is on the way!
I’ll go over a few things that I really like and don’t like about the experience so far.
The Good
Code completion is extremely boss in MonoDevelop. Here you can see the type-ahead completion and extracted documentation rendering in the IDE while I code:
I love this type of code completion. Even the most tepid command-line programmers can’t poo-poo this kind of functionality.
The Solution View – navigating your files
Next, the IDE does this wonderful thing where only one project is open at a time and provides a clear “solution” view as well as other useful perspectives for viewing, editing, and debugging projects.
Just like Visual Studio, you can see all of the projects and resources associated with your solution and can easily manage them. Projects unrelated to what you are doing are not cluttering your workspace and sucking up valuable space in your IDE.
Debugging
Here you can see code with a breakpoint and interactive debugging being performed from MonoDevelop, it’s very slick!
Here you can see the watch and immediate windows, you might exclaim, “I can’t believe it’s not Visual Studio! Same great taste!”
Overall, the MonoDevelop team has the basics covered and much more, even approaching into addressing all the “nice to have” things as well.
The Bad
Some core library and developer functionality just plain isn’t there. I’m not sure if it’s Mono, MonoDevelop, OS X, or gremlins, but there are certain times that you will see things like this:
Most of the time, this doesn’t affect you, the coverage that you get on the Mono platform is surprisingly good.
The Ugly
If you are trying to run newer versions of MonoDevelop on a Ubuntu distribution, you’re gonna have a bad time. The latest official version of Mono that ships in Ubuntu is 2.08 and I don’t know how long it will be until official support will be available for newer versions – I’m looking at you versions 2.11+, which include native support for Entity Framework.
Regardless of your Mono version, you will sometimes have to do weird things to get your code working. This could be due to programmer laziness because Visual Studio is basically cruise control + autopilot for programmers or it could be due to the very heterogeneous environments in which Mono(Develop) runs in. Regardless of the cause, you have to do some magic bone waving in order to get your project running if you created it on Windows with Visual Studio.
Using MonoDevelop in practice
Speaking of magic bone waving, I got the Google+ C#/.NET Quickstart working from MonoDevelop on OS X! I only had to do one little trick to get DotNetOpenAuth to stop complaining… the following steps should be enough to get you going:
Follow the same instructions as the quickstart except:
- Download the log4net library
- Place the extracted DLL from the Mono folder into the services folder alongside the other libraries
- Profit!
When you run the sample, the server will not by default go to the http://localhost:4567/signin.aspx page. Instead, it will load on the default development port, 8080, and will navigate to index.html the template file used for the project. The easiest way to get around these two things:
- Add http://localhost:8080 to your project in the Google APIs console
- Manually enter the URL http://localhost:8080 into your browser when the code runs
That’s it!
Feel free to let me know what you think of MonoDevelop and whether you have any tips for using it, my first foray into using it has shown this to be a very promising, intuitive, and clean IDE. If only I could develop and manage Java projects with such a nice UI and the wonderful code completion and documentation integration…
Before we begin, you can get MonoDevelop from here: MonoDevelop
I have been playing a little bit with MonoDevelop as an alternative to running a VM from OS X for C# development. After spending a little time with it, MonoDevelop really isn’t that bad…
(I can hear the *hissing* from naysayers)
… Really! It’s not Visual Studio, mind you, but it’s still a great IDE. For OS X, the latest versions have pretty awesome coverage of the .NET 4.0 specification and C# 4. Even a good portion of the .NET 4.5 specification is supported and C# 5 is on the way!
I’ll go over a few things that I really like and don’t like about the experience so far.
The Good
Code completion is extremely boss in MonoDevelop. Here you can see the type-ahead completion and extracted documentation rendering in the IDE while I code:
I love this type of code completion. Even the most tepid command-line programmers can’t poo-poo this kind of functionality.
The Solution View – navigating your files
Next, the IDE does this wonderful thing where only one project is open at a time and provides a clear “solution” view as well as other useful perspectives for viewing, editing, and debugging projects.
Just like Visual Studio, you can see all of the projects and resources associated with your solution and can easily manage them. Projects unrelated to what you are doing are not cluttering your workspace and sucking up valuable space in your IDE.
Debugging
Here you can see code with a breakpoint and interactive debugging being performed from MonoDevelop, it’s very slick!
Here you can see the watch and immediate windows, you might exclaim, “I can’t believe it’s not Visual Studio! Same great taste!”
Overall, the MonoDevelop team has the basics covered and much more, even approaching into addressing all the “nice to have” things as well.
The Bad
Some core library and developer functionality just plain isn’t there. I’m not sure if it’s Mono, MonoDevelop, OS X, or gremlins, but there are certain times that you will see things like this:
Most of the time, this doesn’t affect you, the coverage that you get on the Mono platform is surprisingly good.
The Ugly
If you are trying to run newer versions of MonoDevelop on a Ubuntu distribution, you’re gonna have a bad time. The latest official version of Mono that ships in Ubuntu is 2.08 and I don’t know how long it will be until official support will be available for newer versions – I’m looking at you versions 2.11+, which include native support for Entity Framework.
Regardless of your Mono version, you will sometimes have to do weird things to get your code working. This could be due to programmer laziness because Visual Studio is basically cruise control + autopilot for programmers or it could be due to the very heterogeneous environments in which Mono(Develop) runs in. Regardless of the cause, you have to do some magic bone waving in order to get your project running if you created it on Windows with Visual Studio.
Using MonoDevelop in practice
Speaking of magic bone waving, I got the Google+ C#/.NET Quickstart working from MonoDevelop on OS X! I only had to do one little trick to get DotNetOpenAuth to stop complaining… the following steps should be enough to get you going:
Follow the same instructions as the quickstart except:
- Download the log4net library
- Place the extracted DLL from the Mono folder into the services folder alongside the other libraries
- Profit!
When you run the sample, the server will not by default go to the http://localhost:4567/signin.aspx page. Instead, it will load on the default development port, 8080, and will navigate to index.html the template file used for the project. The easiest way to get around these two things:
- Add http://localhost:8080 to your project in the Google APIs console
- Manually enter the URL http://localhost:8080 into your browser when the code runs
That’s it!
Feel free to let me know what you think of MonoDevelop and whether you have any tips for using it, my first foray into using it has shown this to be a very promising, intuitive, and clean IDE. If only I could develop and manage Java projects with such a nice UI and the wonderful code completion and documentation integration…
android Google Google+ howtos java Plus programming: android google java programming
by gguuss
leave a comment
Targeting Interactive Post recipients in Android
In the April 9th, 2013 Google+ Developers Live, Chirag and I demonstrated how to set the recipients for an Interactive Post in Android. You can watch the video below:
Code from the show
I have added a gist to GitHub for the code showing how I targeted Android interactive shares by combining the listPeopleActivity into the ShareActivity. The following snippets cover the relevant code changes relative to the Android quickstart sample:
Adding the PlusClient.OnPeopleLoadedListenter interface, for the asynchronous method from loadPeople:
public class ShareActivity extends FragmentActivity implements View.OnClickListener,
OnSignedInListener,
PlusClient.OnPeopleLoadedListener
{
A few variables for holding the PlusClient, used for getting the Interactive Share intent, and the ArrayList of people, populated in the onPeopleLoaded callback.
// Add lists for holding the people from people.list
private ArrayList<Person> mBestRecipients;
private PlusClient mPlusClient;
The onPeopleLoaded asynchronous callback implementation:
// The onPeopleLoaded method for the callback handling the response when the visible
// people are listed
@Override
public void onPeopleLoaded(ConnectionResult status, PersonBuffer personBuffer,
String nextPageToken) {
if (status.getErrorCode() == ConnectionResult.SUCCESS) {
mBestRecipients.clear();
try {
int count = personBuffer.getCount();
for (int i = 0; i < count; i++) {
Person toAdd = personBuffer.get(i).freeze();
mBestRecipients.add(toAdd);
}
} finally {
personBuffer.close();
}
} else {
Log.e(TAG, "Error when listing people: " + status);
}
Intent intent = getInteractivePostIntent(mPlusClient);
startActivityForResult(intent, REQUEST_CODE_INTERACTIVE_POST);
}
The code for adding the recipients to the post:
if (best){
// Use the recipients list created from people.list...best
builder.setRecipients(mBestRecipients);
}
Additional Notes, Errata
There were a few topics that we would have liked to cover in the episode but just didn’t have enough time for, ended up getting glossed over, or might have been lost when we had recorded live.
While I was setting up the APIs console, I mentioned putting in a meaningful name for the project name. What I should have indicated was to use an appropriate brand name for your company and an appropriate name for this particular client ID. When you are setting up the project, you want to make sure to get this right because it can be difficult to change it later on.
I really would have liked to show adding specific users by name. For example, I was missing Chirag from my list of “BEST” people to share with. If in code, I wanted to make sure that Chirag was always added as a recipient, I would add him by id using the createPerson method from the platform:
// Create an interactive post builder.
PlusShare.Builder builder = new PlusShare.Builder(this, plusClient);
builder.addCallToAction(LABEL_VIEW_ITEM, callToActionUrl, callToActionDeepLinkId)
.setContentUrl(Uri.parse(getString(R.string.plus_example_deep_link_url)))
.setContentDeepLinkId(getString(R.string.plus_example_deep_link_id),
null, null, null)
.setText(mEditSendText.getText().toString());
if (best){
// Use the recipients list created from people.list...best
<strong> mBestRecipients.add(PlusShare.createPerson("118051310819094153327","Chirag Shah"));</strong>
builder.setRecipients(mBestRecipients);
}
In the above code snippet, you can see how I have created the recipient, Chirag Shah in this case, without using the list of people and instead directly adding by ID and display name. In practice, this is a more common use case and would probably be done using your own database of users.
Speaking of, I also would have liked to have had a longer discussion on who to target posts to. For example, you could look at the PhotoHunt app that we ship as part of the Google+ platform documentation. In this sample, we create an internal social graph of users within the app to people they know on Google+ who have installed the app. For that app, we could target interactive posts to the people who the current user has in their Circles on Google+ and who have already installed the app.
Another note on the code I showed on the gist, the best practice for Android would be to pre-load the list of people when the activity loads as opposed to waiting until the user clicks. That way, the UI doesn’t lag in cases where there is no internet connectivity. Because I wanted to show the before and after code and lazily copied the code from the ListPeopleActivity, I didn’t end up doing this.
A HUGE point that we didn’t cover was how the targeted posts appear across all platforms. When you specify a specific person, they will get notifications across all of the places that the notifications appear:
- Search
- GMail
- Google+
- Mobile
- and so on
As such, these targeted interactive posts are much more effective at reaching the people listed in the post.
Hope you enjoyed the GDL, it was fun to demo! Here’s a snapshot of Chirag and me during the session:
In the April 9th, 2013 Google+ Developers Live, Chirag and I demonstrated how to set the recipients for an Interactive Post in Android. You can watch the video below:
Code from the show
I have added a gist to GitHub for the code showing how I targeted Android interactive shares by combining the listPeopleActivity into the ShareActivity. The following snippets cover the relevant code changes relative to the Android quickstart sample:
Adding the PlusClient.OnPeopleLoadedListenter interface, for the asynchronous method from loadPeople:
public class ShareActivity extends FragmentActivity implements View.OnClickListener,
OnSignedInListener,
PlusClient.OnPeopleLoadedListener
{
A few variables for holding the PlusClient, used for getting the Interactive Share intent, and the ArrayList of people, populated in the onPeopleLoaded callback.
// Add lists for holding the people from people.list
private ArrayList<Person> mBestRecipients;
private PlusClient mPlusClient;
The onPeopleLoaded asynchronous callback implementation:
// The onPeopleLoaded method for the callback handling the response when the visible
// people are listed
@Override
public void onPeopleLoaded(ConnectionResult status, PersonBuffer personBuffer,
String nextPageToken) {
if (status.getErrorCode() == ConnectionResult.SUCCESS) {
mBestRecipients.clear();
try {
int count = personBuffer.getCount();
for (int i = 0; i < count; i++) {
Person toAdd = personBuffer.get(i).freeze();
mBestRecipients.add(toAdd);
}
} finally {
personBuffer.close();
}
} else {
Log.e(TAG, "Error when listing people: " + status);
}
Intent intent = getInteractivePostIntent(mPlusClient);
startActivityForResult(intent, REQUEST_CODE_INTERACTIVE_POST);
}
The code for adding the recipients to the post:
if (best){
// Use the recipients list created from people.list...best
builder.setRecipients(mBestRecipients);
}
Additional Notes, Errata
There were a few topics that we would have liked to cover in the episode but just didn’t have enough time for, ended up getting glossed over, or might have been lost when we had recorded live.
While I was setting up the APIs console, I mentioned putting in a meaningful name for the project name. What I should have indicated was to use an appropriate brand name for your company and an appropriate name for this particular client ID. When you are setting up the project, you want to make sure to get this right because it can be difficult to change it later on.
I really would have liked to show adding specific users by name. For example, I was missing Chirag from my list of “BEST” people to share with. If in code, I wanted to make sure that Chirag was always added as a recipient, I would add him by id using the createPerson method from the platform:
// Create an interactive post builder.
PlusShare.Builder builder = new PlusShare.Builder(this, plusClient);
builder.addCallToAction(LABEL_VIEW_ITEM, callToActionUrl, callToActionDeepLinkId)
.setContentUrl(Uri.parse(getString(R.string.plus_example_deep_link_url)))
.setContentDeepLinkId(getString(R.string.plus_example_deep_link_id),
null, null, null)
.setText(mEditSendText.getText().toString());
if (best){
// Use the recipients list created from people.list...best
<strong> mBestRecipients.add(PlusShare.createPerson("118051310819094153327","Chirag Shah"));</strong>
builder.setRecipients(mBestRecipients);
}
In the above code snippet, you can see how I have created the recipient, Chirag Shah in this case, without using the list of people and instead directly adding by ID and display name. In practice, this is a more common use case and would probably be done using your own database of users.
Speaking of, I also would have liked to have had a longer discussion on who to target posts to. For example, you could look at the PhotoHunt app that we ship as part of the Google+ platform documentation. In this sample, we create an internal social graph of users within the app to people they know on Google+ who have installed the app. For that app, we could target interactive posts to the people who the current user has in their Circles on Google+ and who have already installed the app.
Another note on the code I showed on the gist, the best practice for Android would be to pre-load the list of people when the activity loads as opposed to waiting until the user clicks. That way, the UI doesn’t lag in cases where there is no internet connectivity. Because I wanted to show the before and after code and lazily copied the code from the ListPeopleActivity, I didn’t end up doing this.
A HUGE point that we didn’t cover was how the targeted posts appear across all platforms. When you specify a specific person, they will get notifications across all of the places that the notifications appear:
- Search
- GMail
- Google+
- Mobile
- and so on
As such, these targeted interactive posts are much more effective at reaching the people listed in the post.
Hope you enjoyed the GDL, it was fun to demo! Here’s a snapshot of Chirag and me during the session:
best practices Google Google+ JavaScript Plus programming: google oauth PHP programming sign-in
by gguuss
leave a comment
Credential recovery: Reconnecting Google+ if something goes wrong
In my last post, I discussed tricks for upgrading tokens and upgrading to the Google+ sign-in button. This spurred some discussion on the Google+ post, most notably the question, “When do we get the refresh token?” In turn, I figured it would be a good time to write up a post on recovering the refresh token and explaining how and when you can get it. Let’s start with how you get offline access through the refresh token.
Getting offline credentials – the refresh token
How about some background on offline credentials! As described in the basics of OAuth documentation and elaborated in my blog post on the One-time-Code flow with the Google+ sign-in button, there are two types of tokens:
- The access token, which expires in 1 hour (3600 seconds for you “seconds since the UNIX Epoch” folks).
- The refresh token, which never expires, but can only be used to get access tokens – not to make API calls.
Again, the refresh token can not be used for performing API calls but only can be used to mint an access token for API access. This is useful on the server-side of your web sites because you don’t need to have your user signed in for operations like writing app activities. When performing client-side operations, you will never directly work with the refresh token because the Google client library will automatically refresh its access token.
In order to get a refresh token from the sign-in button, you must first create the markup for the button and include the data attribute, accesstype, set to offline:
<button
data-scope="https://www.googleapis.com/auth/plus.login"
data-requestvisibleactions="http://schemas.google.com/AddActivity"
data-clientId="YOUR_CLIENT_ID"
data-accesstype="offline"
data-callback="onSignInCallback"
data-theme="dark"
data-cookiepolicy="single_host_origin">
</button>
When the sign-in callback is returned after successful authorization, the argument passed to it will contain an authorization code. This code can then be exchanged for a refresh token and an access token. The Google+ quickstart samples all show how this is done in various languages. As an example, the following PHP code, from the PHP quickstart, shows how code exchange is performed:
$client = new Google_Client();
$client->setApplicationName(APPLICATION_NAME);
$client->setClientId(CLIENT_ID);
$client->setClientSecret(CLIENT_SECRET);
$client->setRedirectUri('postmessage');
$plus = new Google_PlusService($client);
...
// Exchange the OAuth 2.0 authorization code for user credentials.
$client->authenticate($code);
$token = json_decode($client->getAccessToken());
//verify the token
$reqUrl = 'https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=' .
$token->access_token;
$req = new Google_HttpRequest($reqUrl);
Note The refresh token will not always be returned from the code exchange. This is done because the JavaScript client does not need the refresh token. You’re probably wondering now, when do you get an access token…
When do you get a refresh token?
The simple answer is “Any time that the user is prompted to authorize your application, the authorization code can be exchanged for a refresh token.” The more complicated answer is… it depends. The reason for the complexity here is that the new Google+ sign-in button automatically signs the user in without prompting them because this is consistent with the user experience that Google wants for users across the web.
The following conditions are examples of what will cause the user to be prompted to authorize your application:
- The user has disconnected your application and then tries again to sign in
- The permissions that the app is requesting have changed
- You explicitly request the user to reauthorize*
*Note This should only be used in credential recovery scenarios, which will be discussed later in the article.
So there you have it, the authorization code can be exchanged to return a refresh token anytime the user sees this:
Now that you know when a user gets a new refresh token, you might wonder when you need to re-get that refresh token.
When do you need to recover credentials?
There are a number of scenarios for causes to need to recover server-stored credentials:
- The user disconnects your app
- The database gets corrupted
- An authorization code is exchanged and does not return a refresh token – this could happen if the user blocks your XHR somehow or errors at just the right moment, then signs in again.
So, let’s assume that the worst has happened and you have an invalid refresh token. When this happens, you will get an error from the API indicating that the authorization tokens are no longer valid. The following examples show how you can simulate these scenarios.
Example 1: The user disconnected the application but the session persisted
As an example of things going terribly wrong, manually disconnect the PHP starter I set up from a previous post by:
- Connecting the starter project demo
- Visiting your apps page on Google+ and disconnecting the application
Example 2: Ruining the stored credentials
For simplicity, I have added another couple of endpoints to the PHP server, the first endpoint will destroy the credentials in the user’s session:
- /ruin - destroy the user’s session
Now that you’ve created a state where the user credentials are bad, you can see that the data is bad by looking at the credentials as is now enabled through the following API calls I added to the server:
- /tokendump - Show the session token, if you ruined the session, this will be empty
- /tokeninfo - Test the access token in the session, if you ruined the session, this will show an error
- Try to perform the authorized request to list visible people - if you have ruined the session, this will return an error
Let’s take a look at the third approach, trying to list the people circled. After attempting the API call, you will see the following response:
Google_ServiceException: Error calling GET https://www.googleapis.com/plus/v1/people/me/people/visible: (401) Invalid Credentials
The 401 error, “invalid credentials”, indicates that something wrong has happened. In this case, the user’s account needs to be recovered and access tokens need to be regenerated.
If you were to perform a check against the tokeninfo endpoint for an invalid access token, you would see an error as follows:
{"error":"invalid_token","error_description":"Invalid Value"}
Finally, if the user disconnected their account and you checked a token against tokeninfo, you would see the same error:
{"error":"invalid_token","error_description":"Invalid Value"}
Let’s finish with a discussion of how you can recover the user’s credentials.
How do you recover the credentials?
So what do you do!??!! You now know that you need to regenerate the authorization credentials as indicated from a server-side call. If the user disconnected your account (Example 1 from the previous section), the answer is simple: the next time the user signs in, you exchange the authorization code for a refresh token, update your database, and are done. If the user does not sign-in again with your application, you should remove any stored data that must be removed as described in the Google+ Developer Platform policies.
So, let’s say the user hasn’t disconnected but their authorization data is no longer valid. In this more complicated case (Example 2), the user hasn’t disconnected from your application. If the user returns to the site’s main page, only an access token will be returned from the access code. To see this happen:
- /ruin - destroy the user’s session
- /signin.php - this will automatically authorize the user and the code returned will only return an access token
- /tokendump - Test the access token in the session, you can dump the user’s session using this script.
Note There is no refresh token! This is because the consent dialog was not displayed as described in “When do I get a refresh token”.
To address this scenario, you must force the consent dialog to appear so that the user is then prompted for allowing offline access to your application, and then you can exchange the returned authorization code for updated credentials. The following code shows how you can force the consent dialog to render for recovery scenarios:
<button class="g-signin"
data-scope="https://www.googleapis.com/auth/plus.login"
data-requestvisibleactions="http://schemas.google.com/AddActivity"
data-clientId="YOUR_CLIENT_ID"
data-accesstype="offline"
data-approvalprompt="force"
data-callback="onSignInCallback"
data-theme="dark"
data-cookiepolicy="single_host_origin">
</button>
The following demo recovers the user’s account.
This code, which runs server-side, first removes the invalid session credentials. Next, it forces the approval dialog to appear with the aforementioned markup. Finally, it exchanges the returned code for a new refresh/access token.
Conclusions
Be prepared for the worst and support account recovery on your sites for the rare case in which the credentials you have are bad. You should check that you are getting the refresh token when you expect it in order to make sure that you aren’t storing invalid credentials. If you wanted to be extra careful, you could occasionally audit your credentials database for accounts that have invalid refresh / access tokens for Google+.
In my last post, I discussed tricks for upgrading tokens and upgrading to the Google+ sign-in button. This spurred some discussion on the Google+ post, most notably the question, “When do we get the refresh token?” In turn, I figured it would be a good time to write up a post on recovering the refresh token and explaining how and when you can get it. Let’s start with how you get offline access through the refresh token.
Getting offline credentials – the refresh token
How about some background on offline credentials! As described in the basics of OAuth documentation and elaborated in my blog post on the One-time-Code flow with the Google+ sign-in button, there are two types of tokens:
- The access token, which expires in 1 hour (3600 seconds for you “seconds since the UNIX Epoch” folks).
- The refresh token, which never expires, but can only be used to get access tokens – not to make API calls.
Again, the refresh token can not be used for performing API calls but only can be used to mint an access token for API access. This is useful on the server-side of your web sites because you don’t need to have your user signed in for operations like writing app activities. When performing client-side operations, you will never directly work with the refresh token because the Google client library will automatically refresh its access token.
In order to get a refresh token from the sign-in button, you must first create the markup for the button and include the data attribute, accesstype, set to offline:
<button
data-scope="https://www.googleapis.com/auth/plus.login"
data-requestvisibleactions="http://schemas.google.com/AddActivity"
data-clientId="YOUR_CLIENT_ID"
data-accesstype="offline"
data-callback="onSignInCallback"
data-theme="dark"
data-cookiepolicy="single_host_origin">
</button>
When the sign-in callback is returned after successful authorization, the argument passed to it will contain an authorization code. This code can then be exchanged for a refresh token and an access token. The Google+ quickstart samples all show how this is done in various languages. As an example, the following PHP code, from the PHP quickstart, shows how code exchange is performed:
$client = new Google_Client();
$client->setApplicationName(APPLICATION_NAME);
$client->setClientId(CLIENT_ID);
$client->setClientSecret(CLIENT_SECRET);
$client->setRedirectUri('postmessage');
$plus = new Google_PlusService($client);
...
// Exchange the OAuth 2.0 authorization code for user credentials.
$client->authenticate($code);
$token = json_decode($client->getAccessToken());
//verify the token
$reqUrl = 'https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=' .
$token->access_token;
$req = new Google_HttpRequest($reqUrl);
Note The refresh token will not always be returned from the code exchange. This is done because the JavaScript client does not need the refresh token. You’re probably wondering now, when do you get an access token…
When do you get a refresh token?
The simple answer is “Any time that the user is prompted to authorize your application, the authorization code can be exchanged for a refresh token.” The more complicated answer is… it depends. The reason for the complexity here is that the new Google+ sign-in button automatically signs the user in without prompting them because this is consistent with the user experience that Google wants for users across the web.
The following conditions are examples of what will cause the user to be prompted to authorize your application:
- The user has disconnected your application and then tries again to sign in
- The permissions that the app is requesting have changed
- You explicitly request the user to reauthorize*
*Note This should only be used in credential recovery scenarios, which will be discussed later in the article.
So there you have it, the authorization code can be exchanged to return a refresh token anytime the user sees this:
Now that you know when a user gets a new refresh token, you might wonder when you need to re-get that refresh token.
When do you need to recover credentials?
There are a number of scenarios for causes to need to recover server-stored credentials:
- The user disconnects your app
- The database gets corrupted
- An authorization code is exchanged and does not return a refresh token – this could happen if the user blocks your XHR somehow or errors at just the right moment, then signs in again.
So, let’s assume that the worst has happened and you have an invalid refresh token. When this happens, you will get an error from the API indicating that the authorization tokens are no longer valid. The following examples show how you can simulate these scenarios.
Example 1: The user disconnected the application but the session persisted
As an example of things going terribly wrong, manually disconnect the PHP starter I set up from a previous post by:
- Connecting the starter project demo
- Visiting your apps page on Google+ and disconnecting the application
Example 2: Ruining the stored credentials
For simplicity, I have added another couple of endpoints to the PHP server, the first endpoint will destroy the credentials in the user’s session:
- /ruin - destroy the user’s session
Now that you’ve created a state where the user credentials are bad, you can see that the data is bad by looking at the credentials as is now enabled through the following API calls I added to the server:
- /tokendump - Show the session token, if you ruined the session, this will be empty
- /tokeninfo - Test the access token in the session, if you ruined the session, this will show an error
- Try to perform the authorized request to list visible people - if you have ruined the session, this will return an error
Let’s take a look at the third approach, trying to list the people circled. After attempting the API call, you will see the following response:
Google_ServiceException: Error calling GET https://www.googleapis.com/plus/v1/people/me/people/visible: (401) Invalid Credentials
The 401 error, “invalid credentials”, indicates that something wrong has happened. In this case, the user’s account needs to be recovered and access tokens need to be regenerated.
If you were to perform a check against the tokeninfo endpoint for an invalid access token, you would see an error as follows:
{"error":"invalid_token","error_description":"Invalid Value"}
Finally, if the user disconnected their account and you checked a token against tokeninfo, you would see the same error:
{"error":"invalid_token","error_description":"Invalid Value"}
Let’s finish with a discussion of how you can recover the user’s credentials.
How do you recover the credentials?
So what do you do!??!! You now know that you need to regenerate the authorization credentials as indicated from a server-side call. If the user disconnected your account (Example 1 from the previous section), the answer is simple: the next time the user signs in, you exchange the authorization code for a refresh token, update your database, and are done. If the user does not sign-in again with your application, you should remove any stored data that must be removed as described in the Google+ Developer Platform policies.
So, let’s say the user hasn’t disconnected but their authorization data is no longer valid. In this more complicated case (Example 2), the user hasn’t disconnected from your application. If the user returns to the site’s main page, only an access token will be returned from the access code. To see this happen:
- /ruin - destroy the user’s session
- /signin.php - this will automatically authorize the user and the code returned will only return an access token
- /tokendump - Test the access token in the session, you can dump the user’s session using this script.
Note There is no refresh token! This is because the consent dialog was not displayed as described in “When do I get a refresh token”.
To address this scenario, you must force the consent dialog to appear so that the user is then prompted for allowing offline access to your application, and then you can exchange the returned authorization code for updated credentials. The following code shows how you can force the consent dialog to render for recovery scenarios:
<button class="g-signin"
data-scope="https://www.googleapis.com/auth/plus.login"
data-requestvisibleactions="http://schemas.google.com/AddActivity"
data-clientId="YOUR_CLIENT_ID"
data-accesstype="offline"
data-approvalprompt="force"
data-callback="onSignInCallback"
data-theme="dark"
data-cookiepolicy="single_host_origin">
</button>
The following demo recovers the user’s account.
This code, which runs server-side, first removes the invalid session credentials. Next, it forces the approval dialog to appear with the aforementioned markup. Finally, it exchanges the returned code for a new refresh/access token.
Conclusions
Be prepared for the worst and support account recovery on your sites for the rare case in which the credentials you have are bad. You should check that you are getting the refresh token when you expect it in order to make sure that you aren’t storing invalid credentials. If you wanted to be extra careful, you could occasionally audit your credentials database for accounts that have invalid refresh / access tokens for Google+.
best practices demos Google Google+ hacks JavaScript php Plus programming: google hacks PHP programming tricks
by gguuss
1 comment
Google+ code tricks: Upgrading to the new sign-in / upgrading scopes
I have had many folks ask me how they can do the following things:
- Upgrade from the traditional OAuth flows to the new one-time-code flow
- Authorize with just plus.login and then add additional (optional but useful) services to the user’s account
These are great scenarios where developers and site owners can benefit from understanding the user experience and options that are available when moving forward with the new sign-in features of Google+. In this post, I’ll share some of my personal thoughts and experience with adding, changing, and upgrading OAuth v2 scopes with the sign-in button. Note These are preliminary thoughts on how this could be done and are not official guidance – please let me know if you have additional suggestions for ways that this could be done better!
Upgrade from OAuth v2 to sign-in
In this scenario, you have a site that works with the existing OAuth v2 flows, most likely a server-side flow. If you’re looking to upgrade to use the sign-in button flow, your existing users will need to reauthorize your application. This is because the the sign-in button implicitly is requesting the plus.login scope for sign-in. Let’s take a look at the experience for how this scenario would work!
A demo of the upgrade flow
First, look at this page:
Simple Server-side Sign-in Demo
If the page is not loading, you can logout here. After you connect your account, you will reach an authorization page such as the following:
When you reach the site again, signed in to Google, you will be authorized and the page will know your id and can render your profile.
Let’s now assume we want to upgrade the user’s account to use sign-in in order to add users from Google+ to it. In order to do this, the user must login again, this time using the sign-in button as shown in the following demo:
One-time-code Server-side Sign-in Demo
More on why the user must reauthorize later… but the dialog looks as follows
After authorizing again, the user’s account will then be upgraded to take advantage of the new Google+ Sign-in features.
Let’s now take a look at the steps used to upgrade your backend database to reflect the upgrade.
Updating the user information in the site’s data model
First, recall the simple example where the userinfo.email scope was requested and the user was authorized using the conventional server-side flow. At this point, your site could have a model for the user similar to the following:
Let’s assume that the user has created data associated with that flow, so the (…) could represent additional data specific to your site. Their account ID has been set, because it’s returned with the plus.email scope so the user’s account credentials already exist in this database on the site.
Next, after you have updated your site to have the new sign-in button and the user reauthorizes, you can update the existing data on your site for the user and can add the additional information returned from Google+. From a back-end perspective, the model could now look something like this:
The refresh token and access token, highlighted in red, are updated after the code-exchange happens server-side on the one-time-code flow. This is done because the new credentials have access to the additional scopes. The VisibleUser table is also populated with all of the users visible to the site so that the social graph can be taken advantage of.
Next, let’s take a look at the programming steps to upgrade your site.
Steps to upgrade the site
Let’s take a closer look at what was done to accomplish this upgrade path. First, the scopes on the API project must be updated to include the Google+ API. This is done by turning on the Google+ scope from the API console:
Next, the site needs to be updated to include the new sign-in button as described on the Google+ developers documentation page, Google+ sign-in. Summarized briefly, the markup code needs to be added to the site:
<button
data-scope="https://www.googleapis.com/auth/plus.login"
data-requestvisibleactions="http://schemas.google.com/AddActivity"
data-clientId="YOUR_CLIENT_ID"
data-accesstype="offline"
data-callback="onSignInCallback"
data-theme="dark"
data-cookiepolicy="single_host_origin">
</button>
…and a sign-in callback will need to be created that POSTs data to the server-side component:
/**
* Hides the sign-in button and connects the server-side app after
* the user successfully signs in.
*
* @param {Object} authResult An Object which contains the access token and
* other authentication information.
*/
onSignInCallback: function(authResult) {
$('#authResult').html('Auth Result:<br/>');
for (var field in authResult) {
$('#authResult').append(' ' + field + ': ' + authResult[field] + '<br/>');
}
if (authResult['access_token']) {
// The user is signed in
this.authResult = authResult;
// After we load the Google+ API, render the profile data from Google+.
gapi.client.load('plus','v1',this.renderProfile);
} else if (authResult['error']) {
// There was an error, which means the user is not signed in.
// As an example, you can troubleshoot by writing to the console:
console.log('There was an error: ' + authResult['error']);
$('#authResult').append('Logged out');
$('#authOps').hide('slow');
$('#gConnect').show();
}
console.log('authResult', authResult);
}
You can learn more about the one-time-code flow by starting with the Google+ PHP Quickstart, but I’ll go a little further here. In cases where the site already has a user, the account can be looked up by their Google ID, which was most likely an index used previously when getting the user credentials from the userinfo.email scope. By matching the ID to the one returned from the plus.login scope, the account has been determined to have been created before and the credentials can be updated for the authorization credentials to the Google authentication servers. Once you have identified the user to upgrade, the persisted refresh token and access token need to be replaced with the new ones.
What is happening behind the scenes is that the OAuth flow in the original site is requesting only the userinfo.email scope. In the updated site, the sign-in button is implicitly requesting the plus.login scope so the user must be prompted again for permissions. When the user authorizes again on the new site, the identifier for the user’s Google account stay the same, so the account can again easily be matched up to reflect the updated permissions.
Adding additional scopes to a user’s account
Related to the first example is when you want a user to sign up for a service without needing to grant full permissions to your application and then later needing to add additional, optional, scopes to the associated account. For example, let’s say that you have a service that uses Google+ for login and creating an account but then later you want to add access to a user’s Google Calendar and Google Drive. What you can do is create a configuration page that dynamically sets the scopes on the sign-in button and then renders the button to reauthorize the user as shown before.
Warning: This is a hack that works for now but is not necessarily a best practice, use at your own risk!
Scare text aside… let’s look at how this could work.
- The user signs up, you request only the scopes required for signing up / signing in on your site as seen in the original one-time-code sign-in demo.
- Later, you bring the user to a configuration page where they select various scopes and then dynamically add these scopes to an authorization request.
The following demo performs some tricks to programmatically add and change the scopes that are requested and then render the sign-in button programmatically:
Demo: Tricks and hacks for Google+ Sign-in authorization scopes
The (definitely not production, there are global variables!) code for dynamically changing the scopes is as follows:
var calendarScopeVar = '';
var driveScopeVar= '';
function toggleCalendar(){
if (calendarScopeVar == ''){
calendarScopeVar = ' https://www.googleapis.com/auth/calendar';
}else{
calendarScopeVar = '';
}
render();
}
function toggleDrive(){
if (driveScopeVar == ''){
driveScopeVar = ' https://www.googleapis.com/auth/drive';
}else{
driveScopeVar = '';
}
render();
}
</script>
Checkboxes are added that toggle the scopes:
<input type="checkbox" onClick="toggleCalendar()"></input>Toggle Calendar<br>
<input type="checkbox" onClick="toggleDrive()"></input>Toggle Drive<br>
<div id="outer">
<div id="gConnect">
<button id="customBtn"></button>
</div>
</div>
Every time that the checkboxes are changed, you re-render the sign-in button as shown in the following code.
function render() {
$('#gConnect').empty();
$('#gConnect').html('<button id="customBtn"></button>');
var scope = 'https://www.googleapis.com/auth/plus.login';
scope += ' https://www.googleapis.com/auth/userinfo.email';
scope += calendarScopeVar;
scope += driveScopeVar;
gapi.signin.render('customBtn', {
'callback': 'onSignInCallback',
'clientid': '{{ CLIENT_ID }}',
'cookiepolicy': 'single_host_origin',
'requestvisibleactions': 'http://schemas.google.com/AddActivity',
'scope':scope
});
}
When the user clicks sign-in, the scopes are requested and the user will be re-authorized. The following image shows the dialog that appears when you select all of the optional scopes.
Upon completion of the authorization request, you will again exchange the code server-side to get and update your authorization tokens. After the authorization code is exchanged, the credentials returned will be authorized to have the additional scopes for calendar and/or drive. Upon completion of the authorization flow, you should replace your existing credentials with the updated ones that now have the new authorization parameters set. Note be careful that you don’t remove the scopes that you have already requested and need – plus.login and userinfo.email in this example.
Additional resources
So there you have it, upgrading and replacing scopes using the Google+ sign-in button. This probably has gotten you thinking about how you can do this on your own sites and in your own experiments. The following links should help to get you going and might spark some creativity for you:
I have had many folks ask me how they can do the following things:
- Upgrade from the traditional OAuth flows to the new one-time-code flow
- Authorize with just plus.login and then add additional (optional but useful) services to the user’s account
These are great scenarios where developers and site owners can benefit from understanding the user experience and options that are available when moving forward with the new sign-in features of Google+. In this post, I’ll share some of my personal thoughts and experience with adding, changing, and upgrading OAuth v2 scopes with the sign-in button. Note These are preliminary thoughts on how this could be done and are not official guidance – please let me know if you have additional suggestions for ways that this could be done better!
Upgrade from OAuth v2 to sign-in
In this scenario, you have a site that works with the existing OAuth v2 flows, most likely a server-side flow. If you’re looking to upgrade to use the sign-in button flow, your existing users will need to reauthorize your application. This is because the the sign-in button implicitly is requesting the plus.login scope for sign-in. Let’s take a look at the experience for how this scenario would work!
A demo of the upgrade flow
First, look at this page:
Simple Server-side Sign-in Demo
If the page is not loading, you can logout here. After you connect your account, you will reach an authorization page such as the following:
When you reach the site again, signed in to Google, you will be authorized and the page will know your id and can render your profile.
Let’s now assume we want to upgrade the user’s account to use sign-in in order to add users from Google+ to it. In order to do this, the user must login again, this time using the sign-in button as shown in the following demo:
One-time-code Server-side Sign-in Demo
More on why the user must reauthorize later… but the dialog looks as follows
After authorizing again, the user’s account will then be upgraded to take advantage of the new Google+ Sign-in features.
Let’s now take a look at the steps used to upgrade your backend database to reflect the upgrade.
Updating the user information in the site’s data model
First, recall the simple example where the userinfo.email scope was requested and the user was authorized using the conventional server-side flow. At this point, your site could have a model for the user similar to the following:
Let’s assume that the user has created data associated with that flow, so the (…) could represent additional data specific to your site. Their account ID has been set, because it’s returned with the plus.email scope so the user’s account credentials already exist in this database on the site.
Next, after you have updated your site to have the new sign-in button and the user reauthorizes, you can update the existing data on your site for the user and can add the additional information returned from Google+. From a back-end perspective, the model could now look something like this:
The refresh token and access token, highlighted in red, are updated after the code-exchange happens server-side on the one-time-code flow. This is done because the new credentials have access to the additional scopes. The VisibleUser table is also populated with all of the users visible to the site so that the social graph can be taken advantage of.
Next, let’s take a look at the programming steps to upgrade your site.
Steps to upgrade the site
Let’s take a closer look at what was done to accomplish this upgrade path. First, the scopes on the API project must be updated to include the Google+ API. This is done by turning on the Google+ scope from the API console:
Next, the site needs to be updated to include the new sign-in button as described on the Google+ developers documentation page, Google+ sign-in. Summarized briefly, the markup code needs to be added to the site:
<button
data-scope="https://www.googleapis.com/auth/plus.login"
data-requestvisibleactions="http://schemas.google.com/AddActivity"
data-clientId="YOUR_CLIENT_ID"
data-accesstype="offline"
data-callback="onSignInCallback"
data-theme="dark"
data-cookiepolicy="single_host_origin">
</button>
…and a sign-in callback will need to be created that POSTs data to the server-side component:
/**
* Hides the sign-in button and connects the server-side app after
* the user successfully signs in.
*
* @param {Object} authResult An Object which contains the access token and
* other authentication information.
*/
onSignInCallback: function(authResult) {
$('#authResult').html('Auth Result:<br/>');
for (var field in authResult) {
$('#authResult').append(' ' + field + ': ' + authResult[field] + '<br/>');
}
if (authResult['access_token']) {
// The user is signed in
this.authResult = authResult;
// After we load the Google+ API, render the profile data from Google+.
gapi.client.load('plus','v1',this.renderProfile);
} else if (authResult['error']) {
// There was an error, which means the user is not signed in.
// As an example, you can troubleshoot by writing to the console:
console.log('There was an error: ' + authResult['error']);
$('#authResult').append('Logged out');
$('#authOps').hide('slow');
$('#gConnect').show();
}
console.log('authResult', authResult);
}
You can learn more about the one-time-code flow by starting with the Google+ PHP Quickstart, but I’ll go a little further here. In cases where the site already has a user, the account can be looked up by their Google ID, which was most likely an index used previously when getting the user credentials from the userinfo.email scope. By matching the ID to the one returned from the plus.login scope, the account has been determined to have been created before and the credentials can be updated for the authorization credentials to the Google authentication servers. Once you have identified the user to upgrade, the persisted refresh token and access token need to be replaced with the new ones.
What is happening behind the scenes is that the OAuth flow in the original site is requesting only the userinfo.email scope. In the updated site, the sign-in button is implicitly requesting the plus.login scope so the user must be prompted again for permissions. When the user authorizes again on the new site, the identifier for the user’s Google account stay the same, so the account can again easily be matched up to reflect the updated permissions.
Adding additional scopes to a user’s account
Related to the first example is when you want a user to sign up for a service without needing to grant full permissions to your application and then later needing to add additional, optional, scopes to the associated account. For example, let’s say that you have a service that uses Google+ for login and creating an account but then later you want to add access to a user’s Google Calendar and Google Drive. What you can do is create a configuration page that dynamically sets the scopes on the sign-in button and then renders the button to reauthorize the user as shown before.
Warning: This is a hack that works for now but is not necessarily a best practice, use at your own risk!
Scare text aside… let’s look at how this could work.
- The user signs up, you request only the scopes required for signing up / signing in on your site as seen in the original one-time-code sign-in demo.
- Later, you bring the user to a configuration page where they select various scopes and then dynamically add these scopes to an authorization request.
The following demo performs some tricks to programmatically add and change the scopes that are requested and then render the sign-in button programmatically:
Demo: Tricks and hacks for Google+ Sign-in authorization scopes
The (definitely not production, there are global variables!) code for dynamically changing the scopes is as follows:
var calendarScopeVar = '';
var driveScopeVar= '';
function toggleCalendar(){
if (calendarScopeVar == ''){
calendarScopeVar = ' https://www.googleapis.com/auth/calendar';
}else{
calendarScopeVar = '';
}
render();
}
function toggleDrive(){
if (driveScopeVar == ''){
driveScopeVar = ' https://www.googleapis.com/auth/drive';
}else{
driveScopeVar = '';
}
render();
}
</script>
Checkboxes are added that toggle the scopes:
<input type="checkbox" onClick="toggleCalendar()"></input>Toggle Calendar<br>
<input type="checkbox" onClick="toggleDrive()"></input>Toggle Drive<br>
<div id="outer">
<div id="gConnect">
<button id="customBtn"></button>
</div>
</div>
Every time that the checkboxes are changed, you re-render the sign-in button as shown in the following code.
function render() {
$('#gConnect').empty();
$('#gConnect').html('<button id="customBtn"></button>');
var scope = 'https://www.googleapis.com/auth/plus.login';
scope += ' https://www.googleapis.com/auth/userinfo.email';
scope += calendarScopeVar;
scope += driveScopeVar;
gapi.signin.render('customBtn', {
'callback': 'onSignInCallback',
'clientid': '{{ CLIENT_ID }}',
'cookiepolicy': 'single_host_origin',
'requestvisibleactions': 'http://schemas.google.com/AddActivity',
'scope':scope
});
}
When the user clicks sign-in, the scopes are requested and the user will be re-authorized. The following image shows the dialog that appears when you select all of the optional scopes.
Upon completion of the authorization request, you will again exchange the code server-side to get and update your authorization tokens. After the authorization code is exchanged, the credentials returned will be authorized to have the additional scopes for calendar and/or drive. Upon completion of the authorization flow, you should replace your existing credentials with the updated ones that now have the new authorization parameters set. Note be careful that you don’t remove the scopes that you have already requested and need – plus.login and userinfo.email in this example.
Additional resources
So there you have it, upgrading and replacing scopes using the Google+ sign-in button. This probably has gotten you thinking about how you can do this on your own sites and in your own experiments. The following links should help to get you going and might spark some creativity for you:
Gus Class's portfolio
Social Media Links
Recent Posts
- Sharing: Best practices for targeting Interactive Posts
- Passive Sharing: Writing app activities without target URLs
- Making the most of the Google+ developer site
- Manipulating sites using the DOM editor in Chrome
- Even more common errors with Sign-in
- Google API clients: Why you should use them and how
- Developing in C# from OS X using MonoDevelop
- Targeting Interactive Post recipients in Android
- Credential recovery: Reconnecting Google+ if something goes wrong
- Google+ code tricks: Upgrading to the new sign-in / upgrading scopes
Linkroll
Help I'm trapped in a code factory!
Short programming tutorials and random geek stuff.Tags
- .NET android C# canvas code coding css debugging demos development google google plus gotchas hacking hacks hangouts history html javascript markup oauth PHP plus programming REST robots schema.org sign-in windows XAML
Categories
- android
- best practices
- books
- C++
- canvas
- cracking
- CSharp
- CSS
- Customizing
- debugging
- demos
- development
- DirectX
- gadgets
- Google+
- gotchas
- gusstuff
- Hacking
- hacks
- hangouts
- hardware
- history
- howtos
- HTML
- java
- JavaScript
- Linux
- Mac
- music
- patterns
- Perl
- php
- Plus
- productivity
- programming
- python
- robots
- ruby
- samples
- Schema.org
- Tools
- Uncategorized
- videos
- Windows
- WordPress























