UNPKG

box-chrome-sdk

Version:

A Chrome App SDK for the Box V2 API

1,131 lines (1,077 loc) 117 kB
(function() { /** * @fileoverview Chrome module and angular service. * @author jmeadows */ angular.module('chrome', []).factory('chrome', function() { return window.chrome; }); })(); //---------------------------- (function() { /** * @fileoverview Chrome downloads module and angular service. @see https://developer.chrome.com/extensions/downloads * @author jmeadows */ var downloads = angular.module('chrome.downloads', ['chrome', 'rx']); downloads.service('chromeDownloads', ['chrome', 'rx', function(chrome, rx) { /** * Download a file. * @param {Object} options Options specifying how to perform the download. See https://developer.chrome.com/extensions/downloads#method-download * @returns {Observable} An observable containing the id of the Chrome DownloadItem started by this call to download. */ this.download = function(options) { return rx.Observable.fromChromeCallback(chrome.downloads.download, null, chrome.downloads)(options); }; }]); })(); //---------------------------- (function() { /** * @fileoverview Chrome Identity module and angular service. @see See https://developer.chrome.com/apps/identity * @author jmeadows */ var downloads = angular.module('chrome.identity', ['chrome', 'rx']); downloads.service('chromeIdentity', ['chrome', 'rx', function(chrome, rx) { /** * Download a file. * @param {Object} options Options for how to launch the web auth flow. See https://developer.chrome.com/apps/identity#method-launchWebAuthFlow * @returns {Observable} An observable containing the id of the Chrome DownloadItem started by this call to download. */ this.login = function(options) { if (angular.isDefined(chrome.identity)) { return rx.Observable.fromChromeCallback(chrome.identity.launchWebAuthFlow, null, chrome.identity)(options); } else { return rx.Observable.fromChromeCallback(chrome.runtime.sendMessage, null, chrome.runtime)({ 'function': 'identity.launchWebAuthFlow', params: [options] }); } }; }]); })(); //---------------------------- (function() { /** * @fileoverview chrome.storage An Rx abstraction of the * [chrome.storage API](https://developer.chrome.com/extensions/storage) * @author jmeadows */ var storage = angular.module('chrome.storage', ['chrome', 'rx']); storage.service('chromeStorage', ['chrome', 'rx', function(chrome, rx) { /** * Gets an item from the Chrome local item store. * @param {String} name The name of the item to get from the local item store. * @returns {Observable} An observable containing the object from the local item store with the given name. */ this.getLocal = function(name) { return rx.Observable.fromChromeCallback(chrome.storage.local.get, null, chrome.storage.local)(name) .map(function(result) { return result[name]; }); }; /** * Sets an item in the Chrome local item store. * @param {Object} value The items to save in the local item store. * @returns {Observable} An observable containing a boolean value indicating whether the storage was successful. */ this.setLocal = function(value) { return rx.Observable.fromChromeCallback(chrome.storage.local.set, null, chrome.storage.local)(value); }; /** * Removes an item from the Chrome local item store. * @param {String} name The name of the item to get from the local item store. * @returns {Observable} An observable containing a boolean value indicating whether the removal was successful. */ this.removeLocal = function(name) { return rx.Observable.fromChromeCallback(chrome.storage.local.remove, null, chrome.storage.local)(name); }; }]); })(); //---------------------------- (function() { /** * @fileoverview Module and angular service wrapping window.crypto. * @author jmeadows */ angular.module('crypto', ['rx']).factory('crypto', ['rx', function(rx) { return { 'getRandomValues': function(arr) { return window.crypto.getRandomValues(arr); }, 'digest': function(algorithm, content) { return rx.Observable.fromPromise(window.crypto.subtle.digest(algorithm, content)); } }; }]); })(); //---------------------------- (function() { /** * @fileoverview Module and angular service wrapping json.patch. * @author jmeadows */ angular.module('json.patch', []).factory('jsonpatch', function() { return window.jsonpatch; }); })(); //---------------------------- (function() { /** * @fileoverview Module and angular service wrapping momentJs. * @author jmeadows */ angular.module('moment', []).factory('moment', function() { return window.moment; }); })(); //---------------------------- (function() { /** * @fileoverview Extensions to RxJS for working with Chrome callbacks and events. * @author jmeadows */ /** * Converts a chrome.* API function that takes a callback to an observable sequence, raising an error if * chrome.runtime.lastError is set. * * @param {Function} func Function with a callback as the last parameter to convert to an Observable sequence. * @param {Scheduler} [scheduler] Scheduler to run the function on. If not specified, defaults to Scheduler.timeout. * @param {Mixed} [context] The context for the func parameter to be executed. If not specified, defaults to undefined. * @param {Function} [selector] A selector which takes the arguments from the callback to produce a single item to yield on next. * @returns {Function} A function, when executed with the required parameters minus the callback, produces an Observable sequence with a single value of the arguments to the callback as an array. */ Rx.Observable.fromChromeCallback = function (func, scheduler, context, selector) { if (!scheduler) { scheduler = Rx.Scheduler.immediate; } return function () { var args = Array.prototype.slice.call(arguments, 0); return new Rx.AnonymousObservable(function (observer) { return scheduler.schedule(function () { function handler(e) { if (chrome.runtime.lastError) { observer.onError(chrome.runtime.lastError.message); observer.onCompleted(); } var results = e || [undefined]; if (selector) { try { results = selector(arguments); } catch (err) { observer.onError(err); return; } } else { if (results.length === 1) { results = results[0]; } } observer.onNext(results); observer.onCompleted(); } args.push(handler); func.apply(context, args); }); }); }; }; /** * Creates an observable sequence from an chrome.event. * @param {Function} event The function to add a handler to the emitter. * @param {Function} [selector] A selector which takes the arguments from the event handler to produce a single item to yield on next. * @returns {Observable} An observable sequence which wraps an event from an event emitter */ Rx.Observable.fromChromeEvent = function(event, selector) { return Rx.Observable.fromEventPattern( function(handler) { event.addListener(handler); }, function(handler) { event.removeListener(handler); }, selector ); }; })(); //---------------------------- (function() { /** * @fileoverview An Rx abstraction of Angular's $http service. * @author jmeadows */ var rxHttp = angular.module('rx.http', ['rx']); rxHttp.factory('http', ['$http', 'rx', function($http, rx) { /** * Creates a FormData object for sending a request that contains a file. * @param {Object} data An object containing key-value pairs to be sent with the request. * @returns {FormData} An object that can be sent as a multipart form request. */ function makeFormData(data) { var form = new FormData(); for (var param in data) { if (data.hasOwnProperty(param) && angular.isDefined(data[param])) { var value = data[param]; // This param is a file - the first element is the file object and the second is the filename if (Array.isArray(value)) { value.unshift(param); form.append.apply(form, value); } else { form.append(param, value); } } } return form; } /** * Turns an Angular promise from the $http service into an Rx observable. * @param {String} method The HTTP verb to use in making the request. * @param {String} url The URL for the request. * @param {Object} config Configuration object for how the request should be sent. * @param {Object} data Data to be sent with the request. * @returns {Observable} An observable containing the result of the HTTP request. */ function getObservable(method, url, config, data) { config = config || {}; config.headers = config.headers || {}; return rx.Observable.fromPromise( $http(angular.extend( config, { url: url, method: method }, data ? { data: makeFormData(data), transformRequest: angular.identity, headers: angular.extend(config.headers, {'Content-Type': undefined}) } : {} ))); } /** * Make an HTTP request. * @param {String} method The HTTP verb to use in making the request. * @param {String} url The URL for the request. * @param {Object} config Configuration object for how the request should be sent. * @param {Object} data Data to be sent with the request. * @returns {Observable} An observable containing the result of the HTTP request. */ function request(method, url, config, data) { var observable = getObservable(method, url, config, data), subject = new rx.AsyncSubject(); observable.subscribe(subject); return subject.asObservable(); } return { getObservable: getObservable, request: request }; }]); })(); //---------------------------- (function() { /** * @fileoverview An angular module and service providing OAuth2 access to the SDK. * @author jmeadows */ var auth = angular.module('box.auth', ['rx', 'box.conf', 'chrome.identity', 'crypto']); auth.provider('boxApiAuth', [ 'authUrl', 'clientId', 'clientSecret', 'redirectUri', function(authUrl, clientId, clientSecret, redirectUri) { var webAuthParams = { url: authUrl + '/authorize?client_id=' + clientId + '&response_type=code&redirect_uri=' + redirectUri, interactive: true }; var throwError; throwError = function() { throw new Error('unknown error extracting code'); }; function parseWebAuthResponseWithState(state) { return function parseWebAuthResponse(responseUrl) { if (responseUrl) { var error = responseUrl.match(/[&\?]error=([^&]+)/); if (error) { throw new Error('Error extracting code'); } var csrfState = responseUrl.match(/[&\?]state=([\w\/\-]+)/); if (! csrfState || csrfState[1] !== state) { throw new Error('Returned csrf token does not match the one sent!'); } return responseUrl.match(/[&\?]code=([\w\/\-]+)/)[1]; } else { return throwError(); } }; } this.setThrowError = function(throwErrorFunc) { throwError = throwErrorFunc; }; var refreshObservable = null; this.$get = ['rx', 'chromeIdentity', 'http', 'crypto', function(rx, chromeIdentity, http, crypto) { return { /** * Present the user with a login form from Box, requesting their login and that they grant access to * your application. * @returns {Observable} An observable that will contain an authorization code if the user performs * the login to Box successfully and grants access to your application. */ login: function() { var randomArray = new Uint8Array(16); crypto.getRandomValues(randomArray); var state = 'box_csrf_token_' + Array.prototype.map.call(randomArray, function(i) { var alphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; return alphabet.charAt(i % alphabet.length); }).join(''); var params = JSON.parse(JSON.stringify(webAuthParams)); params.url += '&state=' + state; return chromeIdentity.login(params).map(parseWebAuthResponseWithState(state)); }, /** * Exchange an authorization code for an authorization token. * @param {String} code An authorization code returned from @see login * @returns {Observable} An observable that will contain the HTTP result from the request * to url https://www.box.com/api/oauth2 containing an authorization token and a refresh token. */ getToken: function(code) { return http.request( 'POST', authUrl + '/token', null, { /*eslint-disable camelcase*/ grant_type: 'authorization_code', code: code, client_id: clientId, client_secret: clientSecret, redirect_uri: redirectUri /*eslint-enable camelcase*/ } ); }, /** * Exchange a refresh token for a new authorization token. * @param {String} refreshToken A refresh token returned from @see getToken or this function. * @returns {Observable} An observable that will contain the HTTP result from the request * to url https://www.box.com/api/oauth2 containing an authorization token and a refresh token. */ refreshToken: function(refreshToken) { var subject = new rx.Subject(); if (refreshObservable === null) { refreshObservable = http.request( 'POST', authUrl + '/token', null, { /*eslint-disable camelcase*/ grant_type: 'refresh_token', refresh_token: refreshToken, client_id: clientId, client_secret: clientSecret, redirect_uri: redirectUri /*eslint-enable camelcase*/ } ) .publish() .refCount(); refreshObservable.subscribe( angular.noop, angular.noop, function() { refreshObservable = null; } ); } refreshObservable.subscribe(subject); return subject.asObservable(); } }; }]; }]); })(); //---------------------------- (function() { /** * @fileoverview Configuration information for the SDK. * @author jmeadows */ var conf = angular.module('box.conf', []); //conf.constant('clientSecret', 'uII-----------------------------'); //conf.constant('clientId', 'i3p-----------------------------'); var runtime = chrome.runtime || {}; var runtimeId = runtime.id; conf.constant('authUrl', 'https://www.box.com/api/oauth2'); conf.constant('redirectUri', 'https://' + runtimeId + '.chromiumapp.org/provider_cb'); conf.constant('apiUrl', 'https://api.box.com/2.0'); conf.constant('uploadUrl', 'https://upload.box.com/api/2.0'); })(); //---------------------------- (function() { /** * @fileoverview An Rx abstraction of Angular's $http service, with authorization for the Box API. * @author jmeadows */ var http = angular.module('box.http', ['rx', 'rx.http', 'box.conf', 'chrome.storage', 'crypto', 'moment']); /** * Injects auth token header to http requests. Automatically retrieves new tokens. * * If we don't have an auth token, see if we have a refresh token and try to exchange for an auth token. * If we don't have a refresh token, we need to log in via oauth2. * If we do have an auth token that isn't expired, append it to all outgoing requests. * @param {Object} rx RxJS namespace object * @param {Object} chromeStorage Chrome storage service * @param {Object} http HTTP service * @param {Object} boxApiAuth Box auth service * @param {Object} moment Angular service for moment.js * @returns {Object} Box HTTP service */ http.factory('boxHttp', ['rx', 'chromeStorage', 'http', 'boxApiAuth', 'moment', function(rx, chromeStorage, http, boxApiAuth, moment) { function storeTokens(result) { var refreshMoment = moment(), accessMoment = moment(); refreshMoment.add('days', 60); accessMoment.add('seconds', result.expires_in); chromeStorage.setLocal({ /*eslint-disable camelcase*/ refresh_token: { token: result.refresh_token, expires_at: refreshMoment.toDate().toString() }, access_token: { token: result.access_token, expires_at: accessMoment.toDate().toString() } /*eslint-enable camelcase*/ }).subscribe(angular.noop); } function ejectTokens() { chromeStorage.removeLocal('access_token') .concat(chromeStorage.removeLocal('refresh_token')) .subscribe(angular.noop); } function tryLogin() { return boxApiAuth.login() .flatMap(function(code) { return boxApiAuth.getToken(code); }) .map(function(result) { return result.data; }) .do(storeTokens); } function shouldRejectToken(data) { return !angular.isDefined(data) || !angular.isDefined(data.token) || !angular.isDefined(data.expires_at) || moment().isAfter(moment(data.expires_at)); } function tryRefresh(noLogin) { return chromeStorage.getLocal('refresh_token') .flatMap(function(data) { function loginIfAllowed() { if (noLogin) { return rx.Observable.throw(new Error('Not logged in!')); } return tryLogin(); } if (shouldRejectToken(data)) { return loginIfAllowed(); } return boxApiAuth.refreshToken(data.token) .map(function(result) { return result.data; }) .onErrorResumeNext(rx.Observable.defer(loginIfAllowed).do(ejectTokens)) .take(1); }) .do(storeTokens) .map(function(result) { return { token: result.access_token }; }); } function tryGetAuthToken(noLogin) { return chromeStorage.getLocal('access_token') .flatMap(function(data) { if (shouldRejectToken(data)) { return tryRefresh(noLogin); } return rx.Observable.return(data); }) .map(function(data) { return data.token; }); } function makeRequest(obs, method, url, config, data) { return obs .flatMap(function(token) { angular.extend(config.headers, {Authorization: 'Bearer ' + token}); return http.getObservable(method, url, config, data); }) .flatMap(function(response) { switch (response.status) { case 401: // Unauthorized - need to login again return makeRequest( tryLogin().map(function(data) { return data.access_token; }), method, url, config, data ); case 202: // Download not ready yet - try it later case 429: // Too many requests - wait some time and then try again return makeRequest( rx.Observable.timer(parseInt(response.headers['Retry-After'], 10) * 1000) .flatMap(function() { return tryGetAuthToken(); }), method, url, config, data ); default: return rx.Observable.return(response.data); } }); } function BoxHttp() { } function makeHelper(method) { return function() { var args = Array.prototype.slice.call(arguments, 0); args.unshift(method); return this.request.apply(this, args); }; } BoxHttp.prototype = { request: function(method, url, config, data) { var subject = new rx.AsyncSubject(); config = config || {}; config.headers = config.headers || this.defaultHeaders; makeRequest(tryGetAuthToken(), method, url, config, data).subscribe(subject); return subject.asObservable(); }, get: makeHelper('GET'), post: makeHelper('POST'), put: makeHelper('PUT'), delete: makeHelper('DELETE'), options: makeHelper('OPTIONS'), auth: tryGetAuthToken, defaultHeaders: {} }; BoxHttp.prototype.constructor = BoxHttp; return new BoxHttp(); }]); http.factory('boxHttpResponseInterceptor',['$q', 'apiUrl', function($q, apiUrl){ return { responseError: function(rejection) { try { if (rejection.config.url.indexOf(apiUrl) === 0 && (rejection.status === 401 || rejection.status === 429)) { return rejection; } } catch(err) { return $q.reject(rejection); } return $q.reject(rejection); } }; }]); http.config(['$httpProvider',function($httpProvider) { //Http Intercpetor to check auth failures for xhr requests $httpProvider.interceptors.push('boxHttpResponseInterceptor'); }]); })(); //---------------------------- (function() { /** * @namespace box */ })(); //---------------------------- (function() { /** * @fileoverview Box SDK public interface. * @author jmeadows */ var sdk = angular.module('box.sdk', ['rx', 'chrome.storage', 'box.auth', 'box.conf', 'box.http', 'box.objects', 'box.util']); sdk.factory('boxSdk', [ '$timeout', 'rx', 'boxHttp', 'apiUrl', 'authUrl', 'crypto', 'BoxCollaboration', 'BoxFile', 'BoxFolder', 'BoxTask', 'BoxUser', 'BoxGroup', 'getAll', 'clientId', 'clientSecret', 'chromeStorage', 'BoxComment', 'http', 'boxObjectBase', 'responseTranslator', 'BoxEvent', function( $timeout, rx, boxHttp, apiUrl, authUrl, crypto, BoxCollaboration, BoxFile, BoxFolder, BoxTask, BoxUser, BoxGroup, getAll, clientId, clientSecret, chromeStorage, BoxComment, http, boxObjectBase, responseTranslator, BoxEvent ) { function Sdk() { this.boxHttp = boxHttp; } Sdk.prototype = angular.extend(Object.create(boxObjectBase), { /** * Logs out from Box, revoking the current API auth and refresh tokens. * @returns {Observable} An observable containing the result of the API request. */ logout: function() { return chromeStorage.getLocal('refresh_token') .flatMap(function(data) { return chromeStorage.removeLocal('access_token') .concat(chromeStorage.removeLocal('refresh_token')) .concat(http.request( 'POST', authUrl + '/revoke', null, { /*eslint-disable camelcase*/ client_id: clientId, client_secret: clientSecret, /*eslint-enable camelcase*/ token: data.token } )); }) .takeLast(1); }, /** * Get the folder object with a given id. * @param {String} id The Box folder id identifying the requested folder * @returns {Observable} An observable containing the requested folder object. */ getFolder: function(id) { return this.boxHttp.get(apiUrl + '/folders/' + id).map(function(result) { return new BoxFolder(result); }); }, /** * Get the file object with a given id. * @param {String} id The Box file id identifying the requested file * @returns {Observable} An observable containing the requested file object. */ getFile: function(id) { return this.boxHttp.get(apiUrl + '/files/' + id).map(function(result) { return new BoxFile(result); }); }, /** * Get the folder object with a given id from the trash. * @param {String} id The Box folder id identifying the requested trashed folder * @returns {Observable} An observable containing the requested trashed folder object. */ getTrashedFolder: function(id) { return this.boxHttp.get(apiUrl + '/folders/' + id + '/trash').map(function(result) { return new BoxFolder(result); }); }, /** * Get the file object with a given id from the trash. * @param {String} id The Box file id identifying the requested trashed file * @returns {Observable} An observable containing the requested trashed file object. */ getTrashedFile: function(id) { return this.boxHttp.get(apiUrl + '/files/' + id + '/trash').map(function(result) { return new BoxFile(result); }); }, /** * Get a list of all items in the trash. * @param {String} fields A comma separated list of fields that should be returned for each trashed item * @returns {Observable} An observable sequence of files and/or folders that are in the trash. */ getTrashedItems: function(fields) { var boxHttp = this.boxHttp; return getAll( function(limit, offset) { return boxHttp.get(apiUrl + '/folders/trash/items', { fields: fields, limit: limit, offset: offset }); }, responseTranslator.translateResponse ); }, /** * Get a list of all pending collaborations. * @returns {Observable} An observable sequence of collaboration objects that are still pending. */ getPendingCollaborations: function() { return this.boxHttp.get(apiUrl + '/collaborations?pending').flatMap(function(result) { return rx.Observable.fromArray(result.entries.map(function(entry) { return new BoxCollaboration(entry); })); }); }, /** * Search Box for content. Read more here [http://developers.box.com/docs/#search]. * @param {String} query The string to search for * @param {Object} params Specifies how the query will be executed. * @returns {Observable} An observable sequence of files and folders. */ search: function(query, params) { var boxHttp = this.boxHttp; return getAll( function(limit, offset) { return boxHttp.get(apiUrl + '/search', { params: angular.extend(params || {}, {query: query, limit: limit, offset: offset}) }); }, responseTranslator.translateResponse ); }, /** * Create a new task. * @param {File|Object} item The file the task will be associated with. * @param {Object} params Can include message (string) and/or due_at (timestamp). * @returns {Observable} An observable containing the newly created Task. */ createTask: function(item, params) { return this.boxHttp.post(apiUrl + '/tasks', null, angular.extend(params, { item: { type: 'file', id: item.id }, action: 'review' }) ).map(function(result) { return new BoxTask(result); }); }, /** * Gets information about the logged-in user. * @returns {Observable} An observable containing the User object for the logged-in user. */ getUserInfo: function() { return this.boxHttp.get(apiUrl + '/users/me').map(function(result) { return new BoxUser(result); }); }, /** * Gets all users in the current user's enterprise if the current user is an enterprise admin. * @param {String} filter A string used to filter the results to only users starting with the filter in either the name or the login * @returns {Observable} An observable stream of user objects in the current enterprise. */ getUsers: function(filter) { var boxHttp = this.boxHttp; return getAll( function(limit, offset) { return boxHttp.get(apiUrl + '/users', { /*eslint-disable camelcase*/ params: { filter_term: filter, limit: limit, offset: offset} /*eslint-enable camelcase*/ }); }, function(result) { return new BoxUser(result); } ); }, /** * Creates a new enterprise user if the current user is an enterprise admin. * @param {Object} params A hash of properties for the user. Must contain at least login and name. * @returns {Observable} An observable containing the new user object. */ createUser: function(params) { return this.boxHttp.post(apiUrl + '/users', null, params) .map(function(result) { return new BoxUser(result); }); }, /** * Get all of the groups for the logged-in user. * @returns {Observable} An observable sequence of groups for the logged-in user. */ getGroups: function() { return this.boxHttp.get(apiUrl + '/groups').flatMap(function(result) { return rx.Observable.fromArray(result.entries.map(function(entry) { return new BoxGroup(entry); })); }); }, /** * Create a new group. * @param {String} name The name for the new group * @returns {Observable} An observable containing the newly created group. */ createGroup: function(name) { return this.boxHttp.post(apiUrl + '/groups', null, {name: name}) .map(function(result) { return new BoxGroup(result); }); }, /** * Subscribe to events for the current user. * @param {String} streamPosition The stream position from which to start streaming events. * @returns {Observable} An observable sequence of BoxEvent objects. Disposing of this sequence unsubscribes. */ subscribeToEvents: function(streamPosition) { var boxHttp = this.boxHttp; function subscribe(streamPosition) { return boxHttp.options(apiUrl + '/events') .flatMap(function(result) { return boxHttp.get(result.entries[0].url + '&stream_position=' + streamPosition) .flatMap(function(response) { if (response.message === 'new_change') { var chunk = -1; return rx.Observable.while( function() { return chunk !== 0; }, rx.Observable.defer(function() { return rx.Observable.return(streamPosition); }) .flatMap(function(position) { return boxHttp.get(apiUrl + '/events?stream_position=' + position) .do(function(result) { chunk = result.chunk_size; streamPosition = result.next_stream_position; }) .flatMap(function(result) { return rx.Observable.fromArray(result.entries) .map(function(entry) { return new BoxEvent(entry, result.next_stream_position); }); }); }) ); } else if (response.message === 'reconnect') { return rx.Observable.empty(); } else { return rx.Observable.empty(); } }) .timeout(result.entries[0].retry_timeout * 1000, rx.Observable.defer(function() { return rx.Observable.empty(); })) .map(function(result) { return { result: result, streamPosition: streamPosition }; }); }); } var obs = rx.Observable.return(streamPosition); if (!angular.isDefined(streamPosition)) { obs = boxHttp.get(apiUrl + '/events?stream_position=now') .map(function(result) { return result.next_stream_position; }); } var subject = new rx.ReplaySubject(); obs.subscribe(function(streamPosition) { var currentStreamPosition = streamPosition; function sub() { subscribe(currentStreamPosition) .do(function(result) { currentStreamPosition = result.streamPosition; }) .subscribe( function onNext(result) { subject.onNext(result.result); }, function onError(error) { subject.onError(error); }, function onCompleted() { $timeout(sub, 1); } ); } sub(); }); return subject.asObservable(); } }); return new Sdk(); }]); sdk.config(['boxApiAuthProvider', function(boxApiAuthProvider) { boxApiAuthProvider.setThrowError(function() { throw chrome.runtime.lastError; }); }]); })(); //---------------------------- (function() { /** * @fileoverview Utility functions needed by the SDK. * @author jmeadows */ var util = angular.module('box.util', ['rx']); util.provider('getAll', function() { var defaultLimit = 20; this.setDefaultLimit = function(limit) { defaultLimit = limit; }; this.$get = ['rx', function(rx) { /** * For a paginated API, get all results. * @param {Function} observableFactory A function that can create a request given a limit and offset. * @param {Function} entryTranslator A function that can turn JSON responses into Box objects. * @returns {Observable} An observable sequence of results. */ return function(observableFactory, entryTranslator) { var limit = defaultLimit, offset = 0, total = -1; return rx.Observable.while( function() { return total === -1 || total > offset; }, rx.Observable.defer(function() { return rx.Observable.return({ limit: limit, offset: offset }); }) .flatMap(function(params) { return observableFactory(params.limit, params.offset) .do(function(result) { offset += limit; total = result.total_count; }) .flatMap(function(result) { return rx.Observable.fromArray( result.entries .map(entryTranslator) .filter(function(entry) { return entry !== null; }) ); }); }) ); }; }]; }); util.service('responseTranslator', [function() { var translators = []; this.registerTranslator = function(translator) { translators.push(translator); }; this.translateResponse = function(response) { var objects = translators.map(function(translator) { return translator(response); }).filter(angular.identity); if (objects.length > 0) { return objects[0]; } else { return response; } }; this.registerDefaultTranslator = function(type, Constructor) { this.registerTranslator(function(response) { return response.type === type ? new Constructor(response) : null; }); }; }]); })(); //---------------------------- (function() { /** * @fileoverview The base class for Box objects. * @author jmeadows */ var objects = angular.module('box.objects', ['rx', 'box.conf', 'box.http', 'box.util', 'chrome.downloads']); objects.factory('boxObjectBase', ['apiUrl', 'boxHttp', function(apiUrl, boxHttp) { function BoxHttpProxy(user) { var defaultHeaders = Object.create(this.defaultHeaders || {}); this.defaultHeaders = angular.extend(defaultHeaders, { 'As-User': user.id }); } var Constructor = Object.getPrototypeOf(boxHttp).constructor; BoxHttpProxy.prototype = Object.create(new Constructor()); return { url: function() { return apiUrl + '/' + this.urlType + '/' + this.id; }, /** * Allows enterprise administrators to make API calls for their managed users. * @param {BoxUser} user The user that operations should be made as * @returns {Object} A new instance of the same type as this box object, on which operations will be performed as the specified user. */ asUser: function(user) { var constructor = this.constructor, args = []; function BoxObjectAsUser() { constructor.apply(this, args); } BoxObjectAsUser.prototype = Object.create(constructor.prototype); for (var i = 0; i < constructor.length - 1; i++) { args.push(null); } args.push(this); var boxObjectAsUser = new BoxObjectAsUser(); boxObjectAsUser.boxHttp = new BoxHttpProxy(user); return boxObjectAsUser; } }; }]); })(); //---------------------------- (function() { /** * @fileoverview Box Collaboration object. @see http://developers.box.com/docs/#collaborations * @author jmeadows */ /** * @module BoxCollaboration */ angular.module('box.objects').factory('BoxCollaboration', ['boxHttp', 'boxObjectBase', 'responseTranslator', function(boxHttp, boxObjectBase, responseTranslator) { /** * Box Collaboration object, representing an access control list. * [Learn more](http://developers.box.com/docs/#collaborations). * @param {Object} json Information about the collaboration from an API request. * @constructor */ function Collaboration(json) { angular.extend(this, json); this.boxHttp = boxHttp; } Collaboration.prototype = angular.extend(Object.create(boxObjectBase), { urlType: 'collaborations', /** * Edit the collaboration, changing its [role](https://support.box.com/entries/20366031-what-are-the-different-collaboration-permissions-and-what-access-do-they-provide) * or whether or not the collaboration has been accepted. * @param {String} role The new role for the collaboration. * @param {Boolean} [status] The new status for the collaboration. * @returns {Observable} An observable containing a new, updated collaboration object. */ edit: function(role, status) { return this.boxHttp.put(this.url(), { role: role, status: status }).map(function(result) { return new Collaboration(result); }); }, /** * Delete the collaboration. * @returns {Observable} An observable containing the Box API respons