Skip to content

Commit 1675ad4

Browse files
authored and
committed
Add sample add-on.
1 parent cd27559 commit 1675ad4

File tree

5 files changed

+399
-0
lines changed

5 files changed

+399
-0
lines changed

samples/Add-on/Callback.html

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<!--
2+
* Copyright 2016 Google Inc. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
-->
16+
17+
<html>
18+
<head>
19+
<base target="_top">
20+
<link rel="stylesheet" href="https://ssl.gstatic.com/docs/script/css/add-ons.css">
21+
<script src="https://cdnjs.cloudflare.com/ajax/libs/intercom.js/0.1.4/intercom.min.js"></script>
22+
</head>
23+
<body>
24+
<h1><?= title ?></h1>
25+
<? if (error) { ?>
26+
<p> <span class="error-text">An error has occurred: <?= error ?>.</span></p>
27+
<p>You may close this tab.</p>
28+
<? } else if (isSignedIn) { ?>
29+
<p> <span class="all-clear-text">Signed in!</span>
30+
You may close this tab.</p>
31+
<? } else { ?>
32+
<p> <span class="error-text">Sign in failed.</span>
33+
You may close this tab.</p>
34+
<? } ?>
35+
36+
<script>
37+
var email = '<?= email ?>';
38+
var isSignedIn = '<?= isSignedIn ?>' == 'true';
39+
var error = '<?= error ?>';
40+
41+
var intercom = Intercom.getInstance();
42+
intercom.emit('oauthComplete', {
43+
email: email,
44+
isSignedIn: isSignedIn
45+
});
46+
if (window.top && !error) {
47+
window.top.close();
48+
}
49+
</script>
50+
</body>
51+
</html>

samples/Add-on/Code.gs

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
/**
2+
* Copyright 2016 Google Inc. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
/**
18+
* @OnlyCurrentDoc Limits the add-on to only accessing the current spreadsheet.
19+
*/
20+
21+
/*
22+
* The GitHub OAuth2 client ID and secret.
23+
*/
24+
var CLIENT_ID = '...';
25+
var CLIENT_SECRET = '...';
26+
27+
/**
28+
* Adds a custom menu with items to show the sidebar.
29+
* @param {Object} e The event parameter for a simple onOpen trigger.
30+
*/
31+
function onOpen(e) {
32+
SpreadsheetApp.getUi()
33+
.createAddonMenu()
34+
.addItem('Show Sidebar', 'showSidebar')
35+
.addToUi();
36+
}
37+
38+
/**
39+
* Runs when the add-on is installed; calls onOpen() to ensure menu creation and
40+
* any other initializion work is done immediately.
41+
* @param {Object} e The event parameter for a simple onInstall trigger.
42+
*/
43+
function onInstall(e) {
44+
onOpen(e);
45+
}
46+
47+
/**
48+
* Opens a sidebar. The sidebar structure is described in the Sidebar.html
49+
* project file.
50+
*/
51+
function showSidebar() {
52+
var service = getGitHubService();
53+
var template = HtmlService.createTemplateFromFile('Sidebar');
54+
template.email = Session.getEffectiveUser().getEmail();
55+
template.isSignedIn = service.hasAccess();
56+
var page = template.evaluate()
57+
.setTitle('Sidebar')
58+
.setSandboxMode(HtmlService.SandboxMode.IFRAME);
59+
SpreadsheetApp.getUi().showSidebar(page);
60+
}
61+
62+
/**
63+
* Builds and returns the authorization URL from the service object.
64+
* @return {String} The authorization URL.
65+
*/
66+
function getAuthorizationUrl() {
67+
return getGitHubService().getAuthorizationUrl();
68+
}
69+
70+
/**
71+
* Resets the API service, forcing re-authorization before
72+
* additional authorization-required API calls can be made.
73+
*/
74+
function signOut() {
75+
getGitHubService().reset();
76+
}
77+
78+
/**
79+
* Gets the user's GitHub profile.
80+
*/
81+
function getGitHubProfile() {
82+
var service = getGitHubService();
83+
if (!service.hasAccess()) {
84+
throw 'Error: Missing GitHub authorization.';
85+
}
86+
var url = 'https://api.github.com/user';
87+
var response = UrlFetchApp.fetch(url, {
88+
headers: {
89+
Authorization: 'Bearer ' + service.getAccessToken()
90+
}
91+
});
92+
return JSON.parse(response.getContentText());
93+
}
94+
95+
/**
96+
* Gets the user's GitHub repos.
97+
*/
98+
function getGitHubRepos() {
99+
var service = getGitHubService();
100+
if (!service.hasAccess()) {
101+
throw 'Error: Missing GitHub authorization.';
102+
}
103+
var url = 'https://api.github.com/user/repos';
104+
var response = UrlFetchApp.fetch(url, {
105+
headers: {
106+
Authorization: 'Bearer ' + service.getAccessToken()
107+
}
108+
});
109+
return JSON.parse(response.getContentText());
110+
}
111+
112+
/**
113+
* Gets an OAuth2 service configured for the GitHub API.
114+
* @return {OAuth2.Service} The OAuth2 service
115+
*/
116+
function getGitHubService() {
117+
return OAuth2.createService('github')
118+
// Set the endpoint URLs.
119+
.setAuthorizationBaseUrl('https://github.com/login/oauth/authorize')
120+
.setTokenUrl('https://github.com/login/oauth/access_token')
121+
122+
// Set the client ID and secret.
123+
.setClientId(CLIENT_ID)
124+
.setClientSecret(CLIENT_SECRET)
125+
126+
// Set the name of the callback function that should be invoked to complete
127+
// the OAuth flow.
128+
.setCallbackFunction('authCallback')
129+
130+
// Set the property store where authorized tokens should be persisted.
131+
.setPropertyStore(PropertiesService.getUserProperties())
132+
}
133+
134+
/**
135+
* Callback handler that is executed after an authorization attempt.
136+
* @param {Object} request The results of API auth request.
137+
*/
138+
function authCallback(request) {
139+
var template = HtmlService.createTemplateFromFile('Callback');
140+
template.email = Session.getEffectiveUser().getEmail();
141+
template.isSignedIn = false;
142+
template.error = null;
143+
var title;
144+
try {
145+
var service = getGitHubService();
146+
var authorized = service.handleCallback(request);
147+
template.isSignedIn = authorized;
148+
title = authorized ? 'Access Granted' : 'Access Denied';
149+
} catch (e) {
150+
template.error = e;
151+
title = 'Access Error';
152+
}
153+
template.title = title;
154+
return template.evaluate()
155+
.setTitle(title)
156+
.setSandboxMode(HtmlService.SandboxMode.IFRAME);
157+
}
158+
159+
/**
160+
* Logs the redict URI to register in the Google Developers Console.
161+
*/
162+
function logRedirectUri() {
163+
var service = getGitHubService();
164+
Logger.log(service.getRedirectUri());
165+
}
166+
167+
/**
168+
* Includes the given project HTML file in the current HTML project file.
169+
* Also used to include JavaScript.
170+
* @param {String} filename Project file name.
171+
*/
172+
function include(filename) {
173+
return HtmlService.createHtmlOutputFromFile(filename).getContent();
174+
}

samples/Add-on/JavaScript.html

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
<!--
2+
* Copyright 2016 Google Inc. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
-->
16+
<script>
17+
/**
18+
* Create a wrapped version of google.script.run that
19+
* is adapted for Angular promises.
20+
*/
21+
var ScriptService = function($q) {
22+
var self = this;
23+
var promisify = function(key) {
24+
return function() {
25+
var args = arguments;
26+
return $q(function(resolve, reject) {
27+
google.script.run
28+
.withSuccessHandler(resolve)
29+
.withFailureHandler(reject)
30+
[key].apply(google.script.run, args);
31+
});
32+
};
33+
};
34+
angular.forEach(google.script.run, function(_, key) {
35+
self[key] = promisify(key)
36+
});
37+
};
38+
39+
/**
40+
* The controller for the sidebar.
41+
*/
42+
var SidebarController = function($scope, $window, script, intercom) {
43+
var self = this;
44+
45+
// Hold onto some dependencies.
46+
self.$window = $window;
47+
self.script = script;
48+
49+
// Initialize state.
50+
self.email = $window.email;
51+
self.isSignedIn = $window.isSignedIn;
52+
self.user = null;
53+
self.repos = null;
54+
55+
// Watch for changes to isSignedIn.
56+
$scope.$watch('sidebar.isSignedIn', function(isSignedIn) {
57+
if (isSignedIn) {
58+
script.getGitHubProfile().then(function(user) {
59+
self.user = user;
60+
}, self.handleError);
61+
script.getGitHubRepos().then(function(repos) {
62+
self.repos = repos;
63+
}, self.handleError);
64+
} else {
65+
self.user = null;
66+
self.repos = null;
67+
}
68+
});
69+
70+
// Listen for the event that indicates that the OAuth flow has completed.
71+
intercom.on('oauthComplete', function(data) {
72+
// Make sure the event is for the same Google account.
73+
if (data.email === self.email) {
74+
$scope.$apply(function() {
75+
self.isSignedIn = data.isSignedIn;
76+
});
77+
}
78+
});
79+
};
80+
81+
// Handler for the sign in button.
82+
SidebarController.prototype.signIn = function() {
83+
var self = this;
84+
self.script.getAuthorizationUrl().then(function(authorizationUrl) {
85+
self.$window.open(authorizationUrl);
86+
}, self.handleError);
87+
};
88+
89+
// Handler for the sign out button.
90+
SidebarController.prototype.signOut = function() {
91+
var self = this;
92+
self.script.signOut().then(function() {
93+
self.isSignedIn = false;
94+
}, self.handleError);
95+
};
96+
97+
// A generic handler for script errors.
98+
SidebarController.prototype.handleError = function(error) {
99+
var self = this;
100+
self.$window.alert(error);
101+
};
102+
103+
var module = angular.module('sidebarApp', [])
104+
.service('script', ScriptService)
105+
.service('intercom', function() {
106+
return Intercom.getInstance();
107+
})
108+
.controller('SidebarController', SidebarController);
109+
</script>

samples/Add-on/README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Sample Add-on
2+
3+
This sample add-on for Google Sheets connects to your GitHub account using the
4+
Apps Script OAuth2 library and displays some information about the
5+
repositories you own. It demonstrates some best practices for using this
6+
library in an add-on environment.
7+
8+
## Intercom.js
9+
10+
This add-on uses the [intercom.js library](https://github.com/diy/intercom.js/)
11+
to communicate between tabs / windows. Specifically, the callback page sends a
12+
message to the sidebar letting it know when the authorization flow has
13+
completed, so it can update it can start updating its contents.
14+
15+
## AngularJS
16+
17+
This add-on uses the [AngularJS 1 framework](https://angularjs.org/) to make it
18+
easier to update the sidebar dynamically. It's use is not required, and many
19+
other JavaScript frameworks would work just as well.

0 commit comments

Comments
 (0)