UNPKG

6.08 kBtext/coffeescriptView Raw
1quest = require 'quest'
2dotty = require 'dotty'
3_ = require 'underscore'
4_.str = require 'underscore.string'
5retry = require 'retry'
6
7
8# base google api class. provides functions for:
9# static methods for:
10# 1. Generating an initial oauth redirect with scopes you want
11# 2. Trading in authorization codes for tokens
12# class object is instantiated with oauth token info
13
14class GoogleAPIAdminSDK
15 constructor: (@options) ->
16 throw new Error('Must initialize GoogleAPI with token info') unless @options.token
17 throw new Error('Must provide either a refresh token or an access token') unless @options.token.refresh or @options.token.access
18 # client secret needed for auto-refresh
19 throw new Error('If providing a refresh token, must provide client id and secret') unless not @options.token.refresh or (@options.client?.id and @options.client?.secret)
20
21 retry_options: {maxTimeout: 3 * 60 * 1000, retries: 5, randomize: true}
22 @retry_err: (err, resp) ->
23 return err if err?
24 # 500/502 = "backend error"
25 # 403/503 = rate limiting errors
26 # 404
27 # 412
28 if resp.statusCode in [403, 404, 412, 500, 502, 503]
29 return new Error "Status code #{resp.statusCode} from Google"
30 return null
31
32 # default response interpreters--APIs should most likely specialize these
33 # error_handler returns true if it found/handled an error
34 @error_handler: (cb) ->
35 (err, resp, body) ->
36 return cb(err) or true if err
37 unless 200 <= resp.statusCode < 299
38 return cb(
39 code: resp.statusCode
40 body: body
41 req: _(resp?.req or {}).chain().pick('method', 'path', '_headers').extend({body:"#{resp?.req?.res?.request?.body}"}).value()
42 ) or true
43 return false
44 @response_handler: (cb) ->
45 (err, resp, body) ->
46 return cb err, body if err?
47 return cb null, {'204': 'Operation success'} if resp.statusCode is 204
48 return cb body, null if resp.statusCode >= 400
49 handle_entry = (entry) ->
50 obj = { id: entry.id.$t, updated: entry.updated.$t, link: entry.link, title: entry.title?.$t, feedLink: entry.gd$feedLink, who: entry.gd$who }
51 _(entry).chain().keys().filter((k) -> k.match /^apps\$/).each (apps_key) ->
52 key = apps_key.match(/^apps\$(.*)$/)[1]
53 if key is 'property'
54 obj[prop.name] = prop.value for prop in entry[apps_key]
55 else
56 obj[key] = entry[apps_key]
57 obj
58 if body?.feed?
59 return cb null, { id: body.feed.id, link: body.feed.link, data: _(body.feed.entry or []).map(handle_entry) }
60 else if body?.entry
61 return cb null, handle_entry(body.entry)
62 else if _.str.include(body?.kind, 'admin#directory')
63 return cb null, body
64 else if resp.statusCode is 200 # catch all for success
65
66 return cb null, body
67 else
68 console.log 'WARNING: unhandled body', resp.statusCode, body
69 return cb null, body
70
71 @request_token: (options, cb) =>
72 # need code, client.id, client.secret
73 for required in ['code', 'redirect_uri', 'client.id', 'client.secret']
74 if not dotty.exists(options, required)
75 return cb new Error("Error: '#{required}' is necessary to request a token")
76 options =
77 method: 'post'
78 uri: 'https://accounts.google.com/o/oauth2/token'
79 json: true
80 form:
81 code : options.code
82 redirect_uri : options.redirect_uri
83 client_id : options.client.id
84 client_secret : options.client.secret
85 grant_type : 'authorization_code'
86 operation = retry.operation @retry_options
87 operation.attempt =>
88 quest options, (err, resp, body) =>
89 unless operation.retry GoogleAPIAdminSDK.retry_err(err, resp)
90 (@response_handler(cb)) err, resp, body
91
92 request_refresh_token: (cb) =>
93 options =
94 method: 'post'
95 uri: 'https://accounts.google.com/o/oauth2/token'
96 json: true
97 form:
98 refresh_token : @options.token.refresh
99 client_id : @options.client.id
100 client_secret : @options.client.secret
101 grant_type : 'refresh_token'
102 operation = retry.operation @retry_options
103 operation.attempt =>
104 quest options, (err, resp, body) =>
105 return if operation.retry GoogleAPIAdminSDK.retry_err(err, resp)
106 @options.token.access = body.access_token if body.access_token?
107 @options.token.id = body.id_token if body.id_token?
108 console.log 'Failed to refresh Google token!' if resp.statusCode isnt 200
109 (GoogleAPIAdminSDK.response_handler(cb)) err, resp, body
110
111 oauth2_request: (options, refreshed_already, cb) =>
112 if _(refreshed_already).isFunction()
113 cb = refreshed_already
114 refreshed_already = false
115
116 refresh = () =>
117 @request_refresh_token (err, body) =>
118 return cb err, null, body if err
119 @oauth2_request options, true, cb
120 return refresh() unless @options.token.access
121 options.headers = {} unless options.headers
122 _(options.headers).extend { Authorization: "Bearer #{@options.token.access}" }
123 operation = retry.operation @retry_options
124 operation.attempt =>
125 quest options, (err, resp, body) =>
126 # 401 means invalid credentials so we should refresh our token.
127 # But, if we refreshed_already, then we genuinely have invalid credential problems.
128 if not refreshed_already and resp?.statusCode is 401 and @options.token.refresh
129 return refresh()
130 # keep retrying until there is no err and resp.statusCode is not an error code
131 unless operation.retry GoogleAPIAdminSDK.retry_err(err, resp)
132 cb err, resp, body
133
134 tokeninfo: (cb) =>
135 operation = retry.operation @retry_options
136 operation.attempt =>
137 quest
138 uri: 'https://www.googleapis.com/oauth2/v1/tokeninfo'
139 qs: { access_token: @options.token.access }
140 , (err, resp, body) =>
141 unless operation.retry GoogleAPIAdminSDK.retry_err(err, resp)
142 (GoogleAPIAdminSDK.response_handler(cb)) err, resp, body
143
144module.exports = GoogleAPIAdminSDK