UNPKG

12.5 kBJavaScriptView Raw
1"use strict";
2
3/**
4 * Copyright 2013 Vimeo
5 *
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18var qs_module = require('querystring');
19var url_module = require('url');
20var crypto_module = require('crypto');
21var http_module = require('http');
22var https_module = require('https');
23var FileStreamer = require('./filestreamer');
24
25module.exports.request_defaults = {
26 protocol : 'https:',
27 hostname : 'api.vimeo.com',
28 port : 443,
29 method : 'GET',
30 query : {},
31 headers : {
32 Accept: "application/vnd.vimeo.*+json;version=3.2",
33 'User-Agent': 'Vimeo.js/1.1.1'
34 }
35};
36
37var auth_endpoints = module.exports.auth_endpoints = {
38 authorization : '/oauth/authorize',
39 accessToken : '/oauth/access_token',
40 clientCredentials : '/oauth/authorize/client'
41};
42
43/**
44 * This object is used to interface with the vimeo api
45 *
46 * @param {string} client_id OAuth 2 Client Identifier
47 * @param {string} client_secret OAuth 2 Client Secret
48 * @param (string) access_token OAuth 2 Optional pre-authorized access token
49 */
50var Vimeo = module.exports.Vimeo = function Vimeo (client_id, client_secret, access_token) {
51 this._client_id = client_id;
52 this._client_secret = client_secret;
53
54 if (access_token) {
55 this.access_token = access_token;
56 }
57};
58
59Vimeo.prototype._client_id = null;
60Vimeo.prototype._client_secret = null;
61Vimeo.prototype.access_token = null;
62
63/**
64 * Performs an api call.
65 * Can be called one of two ways.
66 * 1. Url + Callback
67 * If a url is provided, we fill in the rest of the request options with defaults (GET http://api.vimeo.com/{url}).
68 *
69 * 2. Options + callback
70 * If an object is provided, it should match the response of url_module.parse. Path is the only required parameter.
71 *
72 * hostname
73 * port
74 * query (will be applied to the url if GET, request body if POST)
75 * headers
76 * path (can include a querystring)
77 * method
78 *
79 * The callback takes two parameters, err and json.
80 * If an error has occured, your callback will be called as callback(err);
81 * If an error has not occured, your callback will be called as callback(null, json);
82 *
83 * @param {String|Object} options string path (default GET), or object with path, host, port, query, headers
84 * @param {Function} callback called when complete, function (err, json)
85 */
86Vimeo.prototype.request = function vimeo_request (options, callback) {
87 var client = null;
88
89 // if a url was provided, build an options object
90 if (typeof options === "string") {
91 options = url_module.parse(options, true);
92 options.method = "GET";
93 }
94
95 // if we don't have a path at this point, error. a path is the only required field. we have defaults for everything else important
96 if (typeof options.path !== "string") {
97 return callback(new Error('You must provide an api path'));
98 }
99
100 // Turn the provided options into options that are valid for client.request
101 var request_options = this._buildRequestOptions(options);
102
103 // Locate the proper client from the request protocol
104 client = request_options.protocol === 'https:' ? https_module : http_module;
105
106 // Perform the vimeo api request
107 var req = client.request(request_options, this._handleRequest(callback));
108
109 // Write the request body
110 if (['POST','PATCH','PUT','DELETE'].indexOf(request_options.method) !== -1) {
111 if (request_options.headers['Content-Type'] === 'application/json') {
112 request_options.body = JSON.stringify(options.query);
113 } else {
114 request_options.body = qs_module.stringify(options.query);
115 }
116
117 if (request_options.body) {
118 req.write(request_options.body);
119 }
120 }
121
122 // notify user of any weird connection/request errors
123 req.on('error', function(e) {
124 callback(e);
125 });
126
127 // send the request
128 req.end();
129};
130
131/**
132 * Creates the standard request handler for http requests
133 *
134 * @param {Function} callback
135 * @return {Function}
136 */
137Vimeo.prototype._handleRequest = function (callback) {
138 return function (res) {
139 res.setEncoding('utf8');
140
141 var buffer = '';
142
143 res.on('readable', function () {
144 buffer += res.read() || '';
145 });
146
147 if (res.statusCode >= 400) {
148 // failed api calls should wait for the response to end and then call the callback with an error.
149 res.on('end', function () {
150 var err = new Error(buffer);
151 callback(err, buffer, res.statusCode, res.headers);
152 });
153 } else {
154 // successful api calls should wait for the response to end and then call the callback with the response body
155 res.on('end', function () {
156 try {
157 var body = buffer.length ? JSON.parse(buffer) : {};
158 } catch (e) {
159 return callback(buffer, buffer, res.statusCode, res.headers);
160 }
161 callback(null, body, res.statusCode, res.headers);
162 });
163 }
164 };
165};
166
167/**
168 * Merge the request options defaults into the request options
169 *
170 * @param {Object} options
171 * @return {Object}
172 */
173Vimeo.prototype._buildRequestOptions = function (options) {
174 // set up the request object. we always use the options paramter first, and if no value is provided we fall back to request defaults
175 var request_options = this._applyDefaultRequestOptions(options);
176
177 // Apply the access tokens
178 if (this.access_token) {
179 request_options.headers.Authorization = 'Bearer ' + this.access_token;
180 } else if (this._client_id && this._client_secret) {
181 request_options.headers.Authorization = 'Basic ' + new Buffer(this._client_id + ':' + this._client_secret).toString('base64');
182 }
183
184 // Set proper headers for POST, PATCH and PUT bodies
185 if (['POST','PATCH','PUT','DELETE'].indexOf(request_options.method) !== -1 && !request_options.headers['Content-Type']) {
186 request_options.headers['Content-Type'] = 'application/json';
187
188 // Apply parameters to the url for GET requests
189 } else if (request_options.method === 'GET') {
190 request_options.path = this._applyQuerystringParams(request_options, options);
191 }
192
193 return request_options;
194}
195
196/**
197 * Create an object of request options based on the provided list of options, and the request defaults.
198 *
199 * @param {Object} options
200 * @return {Object}
201 */
202Vimeo.prototype._applyDefaultRequestOptions = function (options) {
203 var request_options = {
204 protocol : options.protocol || module.exports.request_defaults.protocol,
205 host : options.hostname || module.exports.request_defaults.hostname,
206 port : options.port || module.exports.request_defaults.port,
207 method : options.method || module.exports.request_defaults.method,
208 headers : options.headers || {},
209 body : '',
210 path : options.path
211 };
212 var key = null;
213
214 // Apply the default headers
215 if (module.exports.request_defaults.headers) {
216 for (key in module.exports.request_defaults.headers) {
217 if (!request_options.headers[key]) {
218 request_options.headers[key] = module.exports.request_defaults.headers[key];
219 }
220 }
221 }
222
223 return request_options;
224}
225
226/**
227 * Apply the query parameter onto the final request url
228 *
229 * @param {Object} request_options
230 * @param {Object} options
231 * @return {String}
232 */
233Vimeo.prototype._applyQuerystringParams = function (request_options, options) {
234 var querystring = '';
235
236 if (!options.query) {
237 return request_options.path;
238 }
239
240 // If we have parameters, apply them to the url
241 if (Object.keys(options.query).length) {
242 if (request_options.path.indexOf('?') < 0) {
243 // If the existing path does not contain any parameters, apply them as the only options
244 querystring = '?' + qs_module.stringify(options.query);
245 } else {
246 // If the user already added parameters to the url, we want to add them as additional parameters
247 querystring = '&' + qs_module.stringify(options.query);
248 }
249 }
250
251 return request_options.path + querystring;
252}
253
254
255/**
256 * Exchange a code for an access token. This code should exist on your redirect_uri
257 *
258 * @param {String} code the code provided on your redirect_uri
259 * @param {String} redirect_uri the exact redirect_uri provided to buildAuthorizationEndpoint and configured in your api app settings
260 * @return {null}
261 */
262Vimeo.prototype.accessToken = function (code, redirect_uri, fn) {
263 var _self = this;
264
265 this.request({
266 method : 'POST',
267 hostname : module.exports.request_defaults.hostname,
268 path : auth_endpoints.accessToken,
269 query : {
270 grant_type : 'authorization_code',
271 code : code,
272 redirect_uri : redirect_uri
273 },
274 headers : {
275 'Content-Type' : 'application/x-www-form-urlencoded'
276 }
277 }, function (err, body, status, headers) {
278 if (err) {
279 return fn(err, null, status, headers);
280 } else {
281 fn(null, body, status, headers);
282 }
283 });
284};
285
286
287/**
288 * The first step of the authorization process.
289 *
290 * This function returns a url, which the user should be sent to (via redirect or link).
291 * The destination allows the user to accept or deny connecting with vimeo, and accept or deny each of the scopes you requested.
292 * Scopes are passed through the second parameter as an array of strings, or a space delimited list.
293 *
294 * Once accepted or denied, the user is redirected back to the redirect_uri.
295 * If accepted, the redirect url will
296 *
297 * @param {String} redirect_uri The uri that will exchange a code for an access token. Must match the uri in your app settings.
298 * @param {String} scope An array of scopes. see https://developer.vimeo.com/api/authentication#scopes for more
299 * @param {String} state A random state that will be returned to you on your redirect uri.
300 */
301Vimeo.prototype.buildAuthorizationEndpoint = function (redirect_uri, scope, state) {
302 var query = {
303 response_type : 'code',
304 client_id : this._client_id,
305 redirect_uri : redirect_uri
306 };
307
308 if (scope) {
309 if (Array.isArray(scope)) {
310 query.scope = scope.join(' ');
311 } else {
312 query.scope = scope;
313 }
314 } else {
315 query.scope = 'public';
316 }
317
318 if (state) {
319 query.state = state;
320 }
321
322 return module.exports.request_defaults.protocol + '//' + module.exports.request_defaults.hostname + auth_endpoints.authorization + '?' + qs_module.stringify(query);
323};
324
325/**
326 * Generates an unauthenticated access token. This is necessary to make unauthenticated requests
327 *
328 * @param {string} scope An array of scopes. see https://developer.vimeo.com/api/authentication#scopes for more
329 * @param {Function} fn A function that is called when the request is complete. If an error occured the first parameter will be that error, otherwise the first parameter will be null.
330 */
331Vimeo.prototype.generateClientCredentials = function (scope, fn) {
332 var query = {
333 grant_type : 'client_credentials',
334 }
335
336 if (scope) {
337 if (Array.isArray(scope)) {
338 query.scope = scope.join(' ');
339 } else {
340 query.scope = scope;
341 }
342 } else {
343 query.scope = 'public';
344 }
345
346 this.request({
347 method : 'POST',
348 hostname : module.exports.request_defaults.hostname,
349 path : auth_endpoints.clientCredentials,
350 query : query,
351 headers : {
352 'Content-Type' : 'application/x-www-form-urlencoded'
353 }
354 }, function (err, body, status, headers) {
355 if (err) {
356 return fn(err, null, status, headers);
357 } else {
358 fn(null, body, status, headers);
359 }
360 });
361}
362
363/**
364 * Initiate streaming uploads
365 *
366 * @param {string} path The path to the file you wish to upload
367 * @param {Function} callback A function that is called when the upload is complete, or fails.
368 */
369Vimeo.prototype.streamingUpload = function (path, video_uri, callback) {
370 var _self = this;
371
372 if (!callback) {
373 callback = video_uri;
374 video_uri = undefined;
375 }
376
377 var options = {
378 method : video_uri ? 'PUT' : 'POST',
379 path : video_uri ? video_uri + '/files' : '/me/videos',
380 query : {
381 type : 'streaming'
382 }
383 };
384
385 this.request(options, function (err, ticket, status, headers) {
386 if (err) {
387 return callback(err);
388 }
389
390 var file = new FileStreamer(path, ticket.upload_link_secure);
391
392 file.ready(function () {
393 _self.request({
394 method : 'DELETE',
395 path : ticket.complete_uri
396 }, callback);
397 });
398
399 file.error(callback);
400 file.upload();
401 });
402};