1 | quest = require 'quest'
|
2 | dotty = require 'dotty'
|
3 | _ = require 'underscore'
|
4 | _.str = require 'underscore.string'
|
5 | retry = require 'retry'
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 | class 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 |
|
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 |
|
25 |
|
26 |
|
27 |
|
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 |
|
33 |
|
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
|
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 |
|
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 |
|
127 |
|
128 | if not refreshed_already and resp?.statusCode is 401 and @options.token.refresh
|
129 | return refresh()
|
130 |
|
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 |
|
144 | module.exports = GoogleAPIAdminSDK
|