UNPKG

foam-framework

Version:
292 lines (268 loc) 9.89 kB
/** * @license * Copyright 2015 Google Inc. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 */ CLASS({ package: 'foam.apps.builder', name: 'UploadManager', requires: [ 'foam.apps.builder.Identity', 'foam.apps.builder.PackageManager', 'foam.apps.builder.Upload', 'foam.apps.builder.XHRManager', 'foam.dao.EasyDAO', 'foam.metrics.Error', 'foam.metrics.Event', ], imports: [ 'identityManager$', 'metricsDAO', 'sourceManager', ], constants: { UPLOAD_TO_CWS: function(id) { return 'https://www.googleapis.com/upload/chromewebstore/v1.1/items' + (id ? '/' + id : ''); }, PUBLISH_TO_CWS: function(id) { return 'https://www.googleapis.com/chromewebstore/v1.1/items/' + id + '/publish'; }, HEADERS: [ [ /https?:[/][/]www.googleapis.com[/][^?#]*chromewebstore/, { 'x-goog-api-version': '2' }, ], ], }, properties: [ { type: 'foam.apps.builder.ImportExportFlow', name: 'data', }, { type: 'foam.apps.builder.XHRManager', name: 'xhrManager', documentation: function() {/* Construct own manager to ensure that contextual bindings for this upload do not leak into higher-level XHR management contexts. */}, factory: function() { return this.XHRManager.create({}, this.Y); }, }, { type: 'foam.apps.builder.PackageManager', name: 'packageManager', lazyFactory: function() { return this.PackageManager.create({}, this.Y); }, }, { model_: 'foam.core.types.DAOProperty', name: 'uploadCache', lazyFactory: function() { return this.EasyDAO.create({ name: 'uploadCache', model: this.Upload, daoType: 'IDB', cache: true, logging: true, }, this.Y); }, }, ], methods: [ function exportApp(data) { this.exportApp_(this.completeUpload.bind(this)); }, function exportApp_(ret) { aseq( this.sourceManager.aloadSources.bind(this.sourceManager, this.data.config), this.packageManager.prepareSources.bind(this.packageManager, this.data.config), apar( // TODO(markdittmer): We should prompt the user to select an // upload account here. this.getIdentity.bind(this), this.prepareUpload.bind(this)), this.sendUpload.bind(this), this.finalizeUpload.bind(this))(ret); }, function publishApp(data) { aseq( apar( // TODO(markdittmer): We should prompt the user to select an // upload account here. this.getIdentity.bind(this), aseq( this.getUploadHash.bind(this), aif(this.needsUpload.bind(this), this.exportApp_.bind(this), aconstant(data.config)))), this.sendPublish.bind(this)) (this.completePublish.bind(this)); }, function getIdentity(ret) { ret(this.identityManager.getIdentity()); }, function getUploadHash(ret) { this.uploadCache.find(this.data.config.chromeId, { put: function(obj) { ret(obj); }, error: function() { ret() }, }); }, function needsUpload(hash) { return ( ! hash ) || this.data.config.hashCode() !== hash.objectHashCode; }, function sendPublish(ret, identity, config) { if ( ! this.Identity.isInstance(identity) || ! identity.oauth ) { this.data.message = 'Oops! Looks like something went wrong.'; this.data.details = this.getOAuthErrorDetails(identity); this.data.state = 'FAILED'; this.metricsDAO.put(this.Error.create({ name: 'Action:publish:fail - ' + (config.model_.id || config.name_) + ' - Access denied', })); return; } // Bind-and-forget. UploadManager and its XHRManager only live for the // length of one request. this.xhrManager.bindAuthAgent( /^https?:[/][/]www[.]googleapis[.]com/, identity.oauth); this.data.state = 'PUBLISHING'; this.xhrManager.asend( ret, this.PUBLISH_TO_CWS(config.chromeId), JSON.stringify({ // TODO(markdittmer): Change this parameter to "default" when/if we want // one-click-publish-to-the-world. target: 'trustedTesters', }), 'POST'); }, function completePublish(data, xhr, status) { if ( ( ! status ) || data.error ) { this.data.message = 'Oops! Something went wrong during your publish request.'; this.data.details = (data && data.error && data.error.errors) ? // TODO(markdittmer): These may not be internationalized. data.error.errors.map(function(err) { return err.message; }).join(' ') : 'Failed to connect to Chrome Web Store.'; this.data.state = 'FAILED'; this.metricsDAO.put(this.Error.create({ name: 'Action:publish:fail - ' + (this.data.config.model_.id || this.data.config.name_) + ' - Errors ' + errors.join(' '), })); return; } this.metricsDAO.put(this.Event.create({ name: 'Action:publish:finish', label: this.data.config.model_.id || this.data.config.name_, })); this.data.state = 'COMPLETED'; this.data.details = 'App published to the Chrome Web Store.'; ret && ret(this.data.config); }, function prepareUpload(ret, archive) { var indicators = argsToArray(arguments).slice(2); if ( ! archive ) { this.data.message = 'Oops! Looks like something went wrong packaging your app.'; this.data.details = 'Make sure all required fields are filled out.'; this.data.state = 'FAILED'; this.metricsDAO.put(this.Error.create({ name: 'Action:upload:fail - ' + (this.data.config.model_.id || this.data.config.name_) + ' - Packaging error', })); return; } var data = new FormData(); data.append('uploadType', 'media'); data.append('fileupload', archive.toBlob()); ret(data); }, function sendUpload(ret, identity, data) { var config = this.data.config; if ( ! this.Identity.isInstance(identity) || ! identity.oauth ) { this.data.message = 'Oops! Looks like something went wrong.'; this.data.details = this.getOAuthErrorDetails(identity); this.data.state = 'FAILED'; this.metricsDAO.put(this.Error.create({ name: 'Action:upload:fail - ' + (config.model_.id || config.name_) + ' - Access denied', })); return; } // Bind-and-forget. UploadManager and its XHRManager only live for the // length of one request. this.xhrManager.bindAuthAgent( /^https?:[/][/]www[.]googleapis[.]com/, identity.oauth); this.data.state = 'UPLOADING'; var id = config.chromeId; this.xhrManager.asend( ret, this.UPLOAD_TO_CWS(id), data, id ? 'PUT' : 'POST'); }, function finalizeUpload(ret, data, xhr, status) { if ( ( ! status ) || data.uploadState !== 'SUCCESS' ) { this.data.message = 'Oops! Something went wrong during the file upload.'; this.data.details = (data && data.itemError) ? // TODO(markdittmer): These may not be internationalized. data.itemError.map(function(err) { return err.error_detail; }).join(' ') : 'Failed to connect to Chrome Web Store.'; this.data.state = 'FAILED'; this.metricsDAO.put(this.Error.create({ name: 'Action:upload:fail - ' + (this.data.config.model_.id || this.data.config.name_) + ' - Status ' + (status ? data.uploadState : xhr.status), })); return; } this.data.config.chromeId = data.id; this.uploadCache.put(this.Upload.create({ id: data.id, objectHashCode: this.data.config.hashCode(), }, this.Y)); ret && ret(this.data.config); }, function completeUpload(config) { this.metricsDAO.put(this.Event.create({ name: 'Action:upload:finish', label: this.data.config.model_.id || this.data.config.name_, })); this.data.state = 'COMPLETED'; this.data.details = 'App uploaded to your Chrome Web Store dashboard.'; }, function getOAuthErrorDetails(err) { if ( ! err || ! err.message ) { return "Authentication failed: Either we couldn't reach the " + "authentication service or you denied the application permission " + "to upload your app."; } // TODO(markdittmer): We should implement a less frail means of detecting // the type of error received. if ( err.message.indexOf('request failed') >= 0 || err.message.indexOf('Failed to reach') >= 0 ) { return "Authentication failed: We couldn't reach the authentication " + "service."; } else if ( err.message.indexOf('not approve') >= 0 || err.message.indexOf('denied') >= 0 ) { return "Authentication failed: You denied the application permission " + "to upload your app."; } else { return "Authentication failed: Either we couldn't reach the " + "authentication service or you denied the application permission " + "to upload your app."; } }, ], });