-
Notifications
You must be signed in to change notification settings - Fork 20
/
index.js
328 lines (288 loc) · 8.35 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
/*jslint indent: 2, esnext: true*/
/*global require: true*/
/*
* For more details about the ClientLogin authentication check out this:
* http://code.google.com/apis/accounts/docs/AuthForInstalledApps.html
*/
var EventEmitter = require('events').EventEmitter,
util = require('util');
// useragent string
const userAgent = 'GCLNodejs';
// version string
const ver = '0.2.2';
const loginURL = '/accounts/ClientLogin';
const googleHost = 'www.google.com';
const captchaRequiredError = 'CaptchaRequired';
// error messages
const errors = {
captchaMissing: 'User entered captcha is missing',
tokenMissing: 'Login token is missing',
loginFailed: 'Login failed'
};
const events = {
login: 'login',
error: 'error'
};
// Google account types
const accountTypes = {
google: 'GOOGLE', // get authorization for a Google account only
hosted: 'HOSTED', // get authorization for a hosted account only
hostedOrGoogle: 'HOSTED_OR_GOOGLE' // get authorization first for a hosted account; if attempt fails, get authorization for a Google account
};
// http://code.google.com/apis/gdata/faq.html#clientlogin
const services = {
adwords: 'adwords',
analytics: 'analytics',
apps: 'apps',
base: 'gbase',
sites: 'jotspot',
blogger: 'blogger',
book: 'print',
calendar: 'cl',
codesearch: 'codesearch',
contacts: 'cp',
docs: 'writely',
finance: 'finance',
mail: 'mail',
health: 'health',
weaver: 'weaver',
maps: 'local',
picasaweb: 'lh2',
reader: 'reader',
sidewiki: 'annotateweb',
spreadsheets: 'wise',
webmastertools: 'sitemaps',
youtube: 'youtube',
c2dm: 'ac2dm',
voice: 'grandcentral',
fusiontables: 'fusiontables',
sj: 'sj' // undocumented features
};
/**
* Helps to log in to any google service with the clientlogin method
* Google returns 3 values when login was success:
* Auth, SID, LSID
*
* After the login you need to include the Auth value into
* the Authorization HTTP header on each request:
*
* client.request('GET', '...', {
* ...,
* 'Authorization':'GoogleLogin auth=' + googleClientLoginInstance.getAuthId()
* })
*
* @class GoogleClientLogin
* @constructor
* @param {Object} conf An object, with two properties: email and password
* @param {String} conf.email
* @param {String} conf.password
*/
var GoogleClientLogin = function (conf) {
this.conf = conf || {};
// stores the authentication data
this.auths = {};
this.loginProcessing = false;
};
GoogleClientLogin.prototype = {
constructor: GoogleClientLogin
};
util.inherits(GoogleClientLogin, EventEmitter);
/**
* Splits response data into key-value pairs,
* Only for internal usage
* @method _parseData
*/
GoogleClientLogin.prototype._parseData = function (data) {
this.auths = {};
data.split('\n').forEach(function (dataStr) {
var data = dataStr.split('=');
this.auths[data[0]] = data[1];
}.bind(this));
};
/**
* Parses the response of the login
* emits error and login event
* @method _parseLoginResponse
* @param {http.ClientResponse} response The response object
*/
GoogleClientLogin.prototype._parseLoginResponse = function (response) {
var data = '';
response.on('data', function (chunk) {
data += chunk;
}.bind(this));
response.on('error', function (e) {
this.emit(events.error, e);
}.bind(this));
response.on('end', function () {
this.loginProcessing = false;
var statusCode = response.statusCode, error;
this._parseData(data);
if (statusCode >= 200 && statusCode < 300) {
/**
* Fires when login was success
* @event login
*/
this.emit(events.login);
} else {
/**
* Fires when login failed
* @event loginFailed
*/
error = new Error(errors.loginFailed);
error.data = data;
error.response = response;
this.emit(events.error, error);
}
}.bind(this));
};
/**
* Method to find out which account type should we use, default is HOSTED_OR_GOOGLE
* Only for internal usage
* @method _getAccountType
* @param {Object} [params]
* @param {String} [params.accountType]
* @return {String}
*/
GoogleClientLogin.prototype._getAccountType = function (params) {
var output = accountTypes.hostedOrGoogle;
if (typeof params === 'object' && params.accountType === 'string' &&
typeof accountTypes[params.accountType] === 'string') {
output = accountTypes[params.accountType];
} else if (typeof this.conf.accountType === 'string') {
output = accountTypes[this.conf.accountType];
}
return output;
};
/**
* Method to create the content of the login request
* Only for internal usage
* @method _getRequestContent
* @param {Object} params (Optional) You can pass the logincaptcha and
* logintoken and the accountType as properties
* @return string
*/
GoogleClientLogin.prototype._getRequestContent = function (params) {
var output, hasCaptcha, hasToken, error;
output = {
accountType: this._getAccountType(params),
Email: this.conf.email,
Passwd: this.conf.password,
service: services[this.conf.service],
source: userAgent + '_' + ver
};
if (typeof params === 'object') {
hasCaptcha = typeof params.logincaptcha === 'string';
hasToken = typeof params.logintoken === 'string';
if (hasCaptcha && hasToken) {
output.logincaptcha = params.logincaptcha;
output.logintoken = params.logintoken;
// if the captcha or the token is given the other also required
} else if (!hasCaptcha && hasToken) {
error = errors.captchaMissing;
} else if (!hasToken && hasCaptcha) {
error = errors.tokenMissing;
}
}
if (error) {
this.emit(events.error, new Error(error));
output = false;
} else {
output = require('querystring').stringify(output);
}
return output;
};
/**
* Logs in the user
* @method login
* @param {Object} params (optional) You can pass the logincaptcha and
* logintoken and the accountType as properties
*/
GoogleClientLogin.prototype.login = function (params) {
// don't try to log in, if one is already in progress
if (!this.loginProcessing) {
this.loginProcessing = true;
var content, request;
content = this._getRequestContent(params);
if (content !== false) {
request = require('https').request(
{
host: googleHost,
port: 443,
path: loginURL,
method: 'POST',
headers: {
'Content-Length': content.length,
'Content-Type': 'application/x-www-form-urlencoded'
}
},
this._parseLoginResponse.bind(this)
);
//prevents crashing on failure to reach google server
request.on('error', function (err) {
this.emit(events.error, err);
}.bind(this));
request.write(content);
request.end();
}
}
};
/**
* Method to get the AuthId property
* @method getAuthId
* @return {String || undefined} the AuthId or undefined
*/
GoogleClientLogin.prototype.getAuthId = function () {
return this.auths.Auth;
};
/**
* Method to ge the SID property
* @method getSID
* @return {String || undefined} the value of the SID property or undefined
*/
GoogleClientLogin.prototype.getSID = function () {
return this.auths.SID;
};
/**
* Method to get the LSID property
* @method getLSID
* @return {String || undefined} the value of the LSID property or undefined
*/
GoogleClientLogin.prototype.getLSID = function () {
return this.auths.LSID;
};
/**
* Method to get the error code
* @method getError
* @return {Number || undefined} the error code or undefined
*/
GoogleClientLogin.prototype.getError = function () {
return this.auths.Error;
};
/**
* Method to know if captcha is required
* @method isCaptchaRequired
* @return {Boolean}
*/
GoogleClientLogin.prototype.isCaptchaRequired = function () {
return this.getError() === captchaRequiredError;
};
/**
* Method to get the captcha url
* @method getCaptchaUrl
* @return {String || undefined} the value of the CaptchaUrl property or undefined
*/
GoogleClientLogin.prototype.getCaptchaUrl = function () {
return this.auths.CaptchaUrl;
};
/**
* Returns the value of the CaptchaToken property
* @method getCaptchaToken
* @return {String || undefined} string or undefined
*/
GoogleClientLogin.prototype.getCaptchaToken = function () {
return this.auths.CaptchaToken;
};
GoogleClientLogin.errors = errors;
GoogleClientLogin.events = events;
GoogleClientLogin.accountTypes = accountTypes;
exports.GoogleClientLogin = GoogleClientLogin;