diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..52e69f2 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,4 @@ +{ + "extends": "fliplet", + "root": true +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..895873f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules +.history diff --git a/backup/README.md b/backup/README.md deleted file mode 100644 index b17a6ab..0000000 --- a/backup/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# App Submission Widget - -Component to capture App data for App Stores submission diff --git a/backup/build.html b/backup/build.html deleted file mode 100644 index 87c50ae..0000000 --- a/backup/build.html +++ /dev/null @@ -1,9 +0,0 @@ -
- {{#if username}} -

Hi {{username}}!

- It worked - {{else}} -

This is the output of your widget

-

Try saving your username on the left hand side and see what happens!

- {{/if}} -
\ No newline at end of file diff --git a/backup/css/interface.css b/backup/css/interface.css deleted file mode 100644 index 58095f2..0000000 --- a/backup/css/interface.css +++ /dev/null @@ -1,10 +0,0 @@ -.files { - display: flex; - list-style: none; -} - -.files li { - display: flex; - flex-direction: column; - margin: 0 10px 10px 0; -} \ No newline at end of file diff --git a/backup/img/icon.png b/backup/img/icon.png deleted file mode 100644 index c134f34..0000000 Binary files a/backup/img/icon.png and /dev/null differ diff --git a/backup/interface.html b/backup/interface.html deleted file mode 100644 index fba546d..0000000 --- a/backup/interface.html +++ /dev/null @@ -1,480 +0,0 @@ -
-
-

App submissions

- Need help? -
- -
-
-
- - Create new -
-
-
- -
- - diff --git a/backup/js/formValidation.js b/backup/js/formValidation.js deleted file mode 100644 index 501e83a..0000000 --- a/backup/js/formValidation.js +++ /dev/null @@ -1,38 +0,0 @@ - -var requiredList; - -function validateForm(platform, submissionType){ - - var hasError = false; - - requiredList = ['#appName', - '#appIconName', - '#shortdescription', - '#keywords', - '#primaryCategory', - '#description', - '#supportUrl', - '#copyright', - '#firstname', - '#lastname', - '#address', - '#city', - '#country', - '#postcode', - '#phonenumber', - '#email' - ]; - - $('.form-group').removeClass('has-error'); - - for (var input in requiredList){ - if (!$(requiredList[input]).closest( '.form-group' ).hasClass( 'hidden' )) { - if ( $(requiredList[input]).val() === '') { - $(requiredList[input]).closest( '.form-group' ).addClass( 'has-error' ); - hasError = true; - } - } - } - - return hasError; -} diff --git a/backup/js/interface.js b/backup/js/interface.js deleted file mode 100644 index 08c0675..0000000 --- a/backup/js/interface.js +++ /dev/null @@ -1,246 +0,0 @@ - -var $submissions = $('[data-submissions]'); -var $form = $('[data-submission]'); -var currentSubmission; -var submissionType; -var uploadedFiles = {}; - -$('input[type="file"]').each(function () { - $(this).parent().append([ - '', - '' - ].join('')); -}); - -$('[data-create]').click(function (event) { - event.preventDefault(); - - hideForm(); - - Fliplet.App.Submissions.create({ - platform: prompt('Platform', 'ios') - }).then(function () { - loadSubmissions(); - }).catch(function (err) { - alert(err.responseJSON.message); - }); -}); - -$('[data-submissions]').change(function () { - var id = $(this).val(); - - hideForm(); - - if (!id) { - return; // you didn't select a submission - } - - Fliplet.App.Submissions.getById(id).then(function (submission) { - currentSubmission = submission; - fillForm(); - }); -}); - -$('input[type="file"]').change(function () { - var formData = new FormData(); - var files = this.files; - var $field = $(this); - var fieldName = $field.attr('name'); - var $message = $field.parent().find('.message'); - var currentFilesCount = $field.parent().find('.files li').length; - var max = parseInt($field.data('max') || 99); - - $message.removeClass('hidden'); - - if (currentFilesCount >= max) { - event.preventDefault(); - $message.text('You can only upload ' + max + ' files for this field.'); - return; - } - - for (var i = 0; i < files.length; i++) { - formData.append(fieldName, files.item(i)); - } - - $field.val(''); - $message.text('Uploading ' + (files.length+1) + ' files...'); - - Fliplet.Media.Files.upload({ - data: formData - }).then(function (files) { - $message.addClass('hidden'); - $message.text(''); - - files.forEach(function (file) { - addFile($field, file); - }); - }).catch(function (err) { - $message.html(err.message || err.description || err); - }); -}); - -function addFile($field, file) { - var $ul = $field.parent().find('.files'); - var $li = $([ - '
  • ', - '', - 'Delete', - '
  • ' - ].join('')); - - uploadedFiles[file.id] = file; - $ul.append($li); -} - -$('.files').on('click', '[data-delete]', function (event) { - event.preventDefault(); - var $li = $(this).parent(); - $li.remove(); - - Fliplet.Media.Files.delete($li.data('id')); -}); - -$('input[name=submissionType]').change(function(){ - //submissionType = this.value; - showForm(); -}); - -function hideForm() { - currentSubmission = undefined; - $form.addClass('hidden'); - $form[0].reset(); - $form.find('.files').html(''); -} - -function showForm() { - $form.removeClass('hidden'); - $form.find('.ios-only, .android-only, .windows-only, .appstore, .enterprise').addClass('hidden'); - - submissionType = $('input:radio[name=submissionType]:checked').val() - //alert($('input:radio[name=submissionType]').filter(":checked").val()); - - $(".appName-help-block").html("There is a limit of 50 characters, however we recommend keeping this to 23"); - $('input[name="appName"]').attr('maxlength', 50); - - if (currentSubmission.platform === "android"){ - $(".appName-help-block").html("There is a limit of 30 characters"); - $('input[name="appName"]').attr('maxlength', 30); - } - - $form.find('.' + currentSubmission.platform + '-only').removeClass('hidden'); - $form.find('.' + submissionType).removeClass('hidden'); -} - -function fillForm() { - _.forIn(currentSubmission.data, function (value, key) { - var $element = $form.find('[name="' + key + '"]'); - - if (key === 'submissionType' && value !== '') { - submissionType = value; - } - - if ($element.attr('type') === 'file') { - if (Array.isArray(value)) { - value.forEach(function (file) { - addFile($element, file); - }); - } else { - console.warn('Files for field ' + key + ' are expected to be an array.') - } - - return; - } - - if ($element.attr('type') === 'radio') { - $form.find('[name="' + key + '"][value="' + value + '"]').prop("checked", true); - return; - } - - $element.val(value); - }); - - $form.find('[data-save]').toggleClass('hidden', ['started', 'failed'].indexOf(currentSubmission.status) === -1); - $form.find('[data-build]').toggleClass('hidden', ['started', 'failed'].indexOf(currentSubmission.status) === -1); - - showForm(); -} - -function loadSubmissions() { - Fliplet.App.Submissions.get().then(function (submissions) { - $submissions.html(''); - - if (!submissions.length) { - return $submissions.append(''); - } - - $submissions.append(''); - - submissions.forEach(function (s) { - $submissions.append(''); - }); - }).catch(function (err) { - alert(err.responseJSON.message); - }); -} - -loadSubmissions(); - -$form.find('[data-build]').click(function (event) { - event.preventDefault(); - - if (validateForm(currentSubmission.platform, submissionType)){ - $('.has-errors:eq(0) input:eq(0)').focus() - return; - } - - saveForm().then(function () { - return Fliplet.App.Submissions.build(currentSubmission.id); - }).then(function () { - hideForm(); - loadSubmissions(); - }).catch(function (err) { - alert(err.responseJSON.message); - }); -}); - -function saveForm() { - var data = $form.serializeArray().reduce(function(obj, item) { - obj[item.name] = item.value; - return obj; - }, {}); - - $('input[type="file"]').each(function () { - var $el = $(this); - var $files = $el.parent().find('.files li'); - var filesList = []; - - $files.each(function () { - filesList.push(uploadedFiles[$(this).data('id')]); - }); - - data[$el.attr('name')] = filesList; - }); - - return Fliplet.App.Submissions.update(currentSubmission.id, data).catch(function (err) { - alert(err.responseJSON.message); - }); -} - -$form.submit(function (event) { - event.preventDefault(); - - if (validateForm(currentSubmission.platform, submissionType)){ - $('.has-errors:eq(0) input:eq(0)').focus(); - return; - } - - saveForm().then(function () { - hideForm(); - loadSubmissions(); - }); -}); - -// Fired from Fliplet Studio when the external save button is clicked -Fliplet.Widget.onSaveRequest(function () { - $form.submit(); -}); \ No newline at end of file diff --git a/backup/tests/index.js b/backup/tests/index.js deleted file mode 100644 index edcfc87..0000000 --- a/backup/tests/index.js +++ /dev/null @@ -1,69 +0,0 @@ -describe('WHEN start component', function() { - this.timeout(10000); - describe('Interface', function() { - it('should have empty username', function(done) { - interfaceBrowser - .evaluate(function (selector) { - return document.querySelector(selector).value; - }, '#username') - .then(function(username) { - expect(username).to.equal(''); - done(); - }) - }); - }); - - describe('Build', function() { - it('should have message to configure widget', function(done) { - const selector = `${buildSelector} h3`; - buildBrowser - .evaluate(function (selector) { - return document.querySelector(selector).textContent; - }, selector) - .then(function(message) { - expect(message).to.equal('This is the output of your widget'); - done(); - }); - }); - }); -}); - -describe('WHEN changing the username', function () { - this.timeout(10000); - const username = casual.username; - - before(function (done) { - interfaceBrowser - .type('#username') - .type('#username', username) - .save() // Use this method to save your widget instance - .then(done) - }); - - describe('Interface', function() { - it('should have new username on the input', function(done) { - interfaceBrowser - .evaluate(function (selector) { - return document.querySelector(selector).value; - }, '#username') - .then(function(inputUsername) { - expect(inputUsername).to.equal(username); - done(); - }) - }); - }); - - describe('Build', function() { - it('should have message with name', function(done) { - const selector = `${buildSelector} h2`; - buildBrowser - .evaluate(function (selector) { - return document.querySelector(selector).textContent; - }, selector) - .then(function(message) { - expect(message).to.equal(`Hi ${username}!`); - done(); - }); - }); - }); -}); diff --git a/backup/widget.json b/backup/widget.json deleted file mode 100644 index 17fe264..0000000 --- a/backup/widget.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "name": "App Submissions", - "package": "com.fliplet.app-submissions", - "version": "1.0.0", - "icon": "img/icon.png", - "tags": [], - "provider_only": true, - "references": [], - "html_tag": "div", - "interface": { - "dependencies": [ - "lodash", - "fliplet-app-submissions", - "fliplet-media", - "fliplet-studio-ui", - "bootstrap" - ], - "assets": [ - "css/interface.css", - "js/formValidation.js", - "js/interface.js" - ] - }, - "build": { - "dependencies": [], - "assets": [] - } -} \ No newline at end of file diff --git a/css/interface.css b/css/interface.css index ea36cba..2d2ab12 100644 --- a/css/interface.css +++ b/css/interface.css @@ -6,6 +6,10 @@ opacity: 0.3; } +.appStore-login-2fa-devices, +.appStore-login-2fa-waiting, +.appStore-login-2fa-code, +.appStore-2fa-sms, .appStore-logged-in, .appStore-teams, .appStore-more-options, @@ -14,6 +18,10 @@ .appStore-generate-file, .appStore-generate-file-success, .appStore-upload-file, +.enterprise-login-2fa-devices, +.enterprise-login-2fa-waiting, +.enterprise-login-2fa-code, +.enterprise-2fa-sms, .enterprise-manual-details, .enterprise-logged-in, .enterprise-teams, @@ -22,7 +30,9 @@ .enterprise-previous-file-success, .enterprise-generate-file, .enterprise-generate-file-success, -.enterprise-upload-file { +.enterprise-upload-file, +.select-proxy-display[for="fl-store-teams"], +.select-proxy-display[for="fl-ent-teams"] { display: none; } @@ -60,6 +70,10 @@ padding-top: 4px; } +.bs-actionsbox .btn-group button { + margin-top: 0; +} + /*@media (min-width: 640px) { .form-horizontal .control-label { padding-top: 12px; @@ -92,6 +106,11 @@ border-color: #a94442; } +.setting-app-icon.default.has-error, +.setting-splash-screen.default.has-error { + border-color: #a94442; +} + .panel.required-fill { -webkit-box-shadow: 0 1px 1px rgba(169, 68, 66, 0.30); box-shadow: 0 1px 1px rgba(169, 68, 66, 0.30); @@ -105,6 +124,10 @@ border-color: #a94442; } +.panel-default.required-fill + .panel-default:not(.required-fill) { + margin-top: 0; +} + .app-details-error, .app-details-warning { display: none; @@ -130,15 +153,31 @@ margin: 0; } -.nav-tabs>li>a span { +.nav-tabs > li > a span { display: block; text-align: center; } -.nav-tabs>li>a span:first-child { +.nav-tabs > li > a span:first-child { font-weight: bold; } +.btn-group-vertical .btn input[type="radio"] { + display: block; + position: absolute; + top: 0; + left: 0; + -webkit-appearance: none; + width: 100%; + height: 100%; + margin: 0; + cursor: pointer; +} + +.btn-group-vertical .btn { + overflow: hidden; + text-overflow: ellipsis; +} /* End Override */ @@ -149,7 +188,7 @@ h3 { color: #14505E; } -body>h3, +body > h3, .app-status h3 { margin-top: 0; } @@ -195,6 +234,21 @@ h3 small { margin-top: -5px; } +.bundleId-ast-text, +.bundleId-ent-text, +.bundleId-uns-text { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.bundleId-ast-text:before, +.bundleId-ent-text:before, +.bundleId-uns-text:before { + content: ''; + display: block; +} + .fl-bundleId-holder, .fl-price-holder { height: 44px; @@ -232,9 +286,9 @@ h3 small { .bootstrap-select .btn-default:active:focus, .bootstrap-select .btn-default:active, .bootstrap-select .btn-default:focus, -.open>.dropdown-toggle.btn-default, -.open>.dropdown-toggle.btn-default:focus, -.open>.dropdown-toggle.btn-default:hover { +.open > .dropdown-toggle.btn-default, +.open > .dropdown-toggle.btn-default:focus, +.open > .dropdown-toggle.btn-default:hover { background-color: transparent; outline: none !important; border-color: #00abd1; @@ -258,9 +312,6 @@ h3 small { .save-appStore-progress, .save-enterprise-progress, .save-unsigned-progress, -.save-appStore-request, -.save-enterprise-request, -.save-unsigned-request, .save-push-progress { opacity: 0; -webkit-transition: opacity 0.25s ease; @@ -270,9 +321,6 @@ h3 small { .save-appStore-progress.saved, .save-enterprise-progress.saved, .save-unsigned-progress.saved, -.save-appStore-request.saved, -.save-enterprise-request.saved, -.save-unsigned-request.saved, .save-push-progress.saved { opacity: 1; } @@ -348,6 +396,7 @@ h3 small { .app-build-status table tbody td { height: 60px; + border: none; } .app-build-status .app-build-holder { @@ -441,24 +490,22 @@ h3 small { .indented-area { background-color: #f2f6f7; border-radius: 6px; - padding: 15px 15px 1px 15px; + padding: 15px 15px 5px; margin-top: 10px; } -.hidden-area { - display: none; - background-color: #f2f6f7; - border-radius: 6px; - padding: 15px 15px 1px 15px; +.alert-inset { margin-top: 10px; margin-bottom: 20px; margin-left: 25px; } -.hidden-area.warning { - color: #8a6d3b; - background-color: #fcf8e3; - border: 1px solid #faebcc; +.alert > p, .alert > ul { + margin-bottom: 10px; +} + +.alert > p:fist-child:last-child, .alert > ul:fist-child:last-child { + margin-bottom: 0; } .radio.radio-icon ~ .radio.radio-icon, @@ -483,4 +530,27 @@ h3 small { .screenshot-previews img { border: 1px solid #F0F0F0; +} + +.screenshot-previews .alert { + margin-bottom: 0; +} + +.analytics-success { + color: rgb(106, 168, 79); +} + +.tooltip-inner { + max-width: 320px; + word-wrap: break-word; +} + +.centered-vertically { + align-items: center; + display: flex; +} + +#fl-push-testResultMessage { + padding: 10px 0; + display: none; } \ No newline at end of file diff --git a/img/app-icon.png b/img/app-icon.png index 768e0e6..68ddea2 100644 Binary files a/img/app-icon.png and b/img/app-icon.png differ diff --git a/interface.html b/interface.html index a3580bf..d1b3673 100644 --- a/interface.html +++ b/interface.html @@ -1,9 +1,5 @@ -

    iOS distribution settings

    -

    Before your app can be distributed, we need some additional information. Select the tab(s) that match how you want to distribute your app and add the data required. Once you submit your information and assets, we will process your request and distribute - your app in your prefered ways within 2 business days. Apple may take up to 5 days to review apps.

    For more information about these fields and examples, see Apple's official recommendations.

    -
    -
    -
    - -
    -
    -

    We will need some technical information now, such as your developer account login details. Don't worry, these details will be safe with us. We need them in order to publish your app for you.

    -

    Apple developer account details and distribution certificates

    - - -
    -
    -
    - -
    -
    -
    - app-developer@fliplet.com - log out -
    -
    -
    -
    -
    -
    -
    - -
    -
    - -
    -
    -
    -
    -
    -

    - For more information on this topic see the Support Documents. + + The release notes are required to let users know what's new in this version. If it's the first version of your app please highlight this. +
    + Use template +

    -
    - - -
    -
    - -

    Certificate Name - Expires on October 30th 2018

    -
    -
    -
    -

    Distribution certificate created successfully.

    -

    Certificate Name - Expires on October 30th 2018

    -
    -
    -
    - - -
    -
    -
    -
    - -
    -
    -
    -
    -
    -

    Distribution certificate created successfully.

    -

    Certificate Name - Expires on October 30th 2018

    -
    -
    - - -
    -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    -
    -
    -
    +
    -
    -
    - -
    -

    This value needs to be unique and the structure must be com.organizationName.appName.

    -
    -
    @@ -775,7 +573,7 @@
    - +
    @@ -784,7 +582,7 @@
    - +
    @@ -793,7 +591,7 @@

    Release Configuration

    +

    How will the app be submitted to review.

    +
    +
    + +

    + With this option, Apple will review and approve your app and you'll need to go into iTunes to manually release the app when you want. +

    +
    +
    +
    + + +
    @@ -1019,7 +835,7 @@

    Reviewer Notes

    - +
    @@ -1028,7 +844,7 @@

    Reviewer Notes

    - +
    @@ -1037,7 +853,7 @@

    Reviewer Notes

    - +
    @@ -1046,8 +862,8 @@

    Reviewer Notes

    - -

    The phone number needs to include the area code. (Eg.: +44)

    + +

    The phone number needs to include the area code. (e.g. +44)

    @@ -1058,7 +874,7 @@

    Reviewer Notes

    Optional

    - +
    @@ -1067,18 +883,302 @@

    Reviewer Notes

    Optional

    - + +
    +
    + Important warning: If the reviewer notes are incomplete or insufficient, Apple may reject the app which will cause a delay in the app launch. We have automatically loaded a suggested template in the reviewer notes. Please make sure to customize this template, or contact the Customer Success team for help. +
    -

    Optional

    +

    + + Use template + +

    +
    +
    + +
    +
    +
    + + + +
    + +
    +
    +
    +

    Fliplet analytics

    +

    Status: Disabled

    +

    Fliplet’s analytics records usage data, including the user’s login if the app has a login, and displays the data within Fliplet Studio. Learn more about Fliplet Analytics.

    +

    To enable in Fliplet Studio visit App Settings > Add-ons tab > Fliplet Analytics

    +
    + +
    +

    Firebase analytics

    +

    Status: Disabled

    +

    Google Firebase offers analytics via Google Analytics and other features. Learn more about Firebase.

    + To enable: +
      +
    1. Login to a Firebase account (any existing Google Account can be used).
    2. +
    3. Create a new app.
    4. +
    5. Select Apple.
    6. +
    7. Download your Firebase plist file.
    8. +
    9. Upload it by clicking the button below:
    10. +
    +
    + +
    +
    +
    + +
    + +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +

    We will need some technical information now, such as your developer account login details. Don't worry, these details will be safe with us. We need them in order to publish your app for you.

    +

    Apple developer account details and distribution certificates

    + + + + + +
    +
    +
    + +
    +
    + +
    +
    +
    +
    +
    +
    + +
    +
    + Load teams + +
    +
    +
    +
    +
    + +

    + The certificate file cannot be password protected. For more information on certificates topic see the Support Documents. +

    +
    +
    +
    + + +
    +
    + +

    Certificate Name - Expires on October 30th 2018

    +
    +
    +
    +

    Distribution certificate created successfully.

    +

    Certificate Name - Expires on (Download: Certificate - Private key)

    +

    +
    +
    + + +
    +
    +

    +

    +
    +
    +

    Distribution certificate created successfully.

    +

    Certificate Name - Expires on (Download: Certificate - Private key)

    +
    +
    + + +
    +
    +
    +

    +

    +

    + + +
    +

    +
    +
    +
    +
    + +

    App-specific password

    +
    +
    + +

    This is required to ensure Fliplet is authorized to submit your app to the store.

    +
    +
    + +
    +
    Please note that you need to generate a unique password for every app.
    See Apple documentation about how to create an app-specific password that Fliplet can use.
    +
    +
    + +

    App version and bundle ID

    +
    +
    + +

    This is the version number of your app and will be displayed on the App Store listing.

    - + +
    +

    The required format for version number is x.x.x, where x is a number, e.g. 1.0.0.

    +
    +
    + +

    This is a unique ID for your app on the App Store. Fliplet auto-generated one for you. The Bundle ID will never be shown to the users.

    +
    +
    + Change +
    +
    +
    +
    + +
    +

    This value needs to be unique and the structure must be com.organizationName.appName.

    +
    +
    +
    +
    @@ -1090,9 +1190,13 @@

    Reviewer Notes

    Your progress was saved successfully!

    + {{#if appFeatures.appStores.apple.public }}

    Submit your information when you are ready!
    We will take care of the rest.

    -

    Your request was sent successfully!

    + {{else}} +

    Upgrade to an app plan to request your app to be published.

    + + {{/if}}
    @@ -1101,7 +1205,7 @@

    Reviewer Notes

    Complete this section to receive an app signed with your Apple Developer Enterprise account to distribute in-house apps to your employees.

    -
    +
    @@ -1130,6 +1235,7 @@
    @@ -1143,23 +1249,68 @@
    +
    +
    +

    Your Apple ID is protected with two-step verification.

    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +

    Apple has sent a verification code to your device(s) from one of Fliplet's server locations. Please enter the code to continue.

    +

    Note: If you don't receive the code, please check your Apple account settings.

    +
    +
    +
    + +
    +
    + +

    Did not get a verification code? Get a text message with a code

    +
    +
    +
    +
    +
    + +
    +
    +
    -

    Warning! This area should only be used if you have technical technical knowledge on enterprise app submissions, p12 files and mobile provisioning files.

    +

    Warning! This area should only be used if you have technical knowledge on enterprise app submissions, p12 files and mobile provisioning files.

    @@ -1241,7 +1429,7 @@

    Apple developer account details and distribution certificates - +

    @@ -1258,7 +1446,7 @@

    Apple developer account details and distribution certificates - +

    @@ -1271,7 +1459,7 @@

    Apple developer account details and distribution certificates
    - app-developer@fliplet.com - log out + - Change account
    @@ -1282,13 +1470,13 @@

    Apple developer account details and distribution certificates
    - + Load teams +
    @@ -1296,10 +1484,11 @@

    Apple developer account details and distribution certificates

    - For more information on this topic see the Support Documents. + For more information on this topic see the Support Documents.

    +
    Certificates generated for iOS last up to 12 months and must be regenerated before they expire.

    Distribution certificate created successfully.

    -

    Certificate Name - Expires on October 30th 2018

    +

    Certificate Name - Expires on (Download: Certificate - Private key)

    @@ -1324,16 +1513,12 @@

    Apple developer account details and distribution certificates

    -
    -
    - -
    -
    -
    +

    +

    Distribution certificate created successfully.

    -

    Certificate Name - Expires on October 30th 2018

    +

    Certificate Name - Expires on (Download: Certificate - Private key)

    @@ -1342,19 +1527,19 @@

    Apple developer account details and distribution certificates

    -
    -
    +
    +

    -

    -
    +

    +

    - +
    -
    +

    @@ -1366,9 +1551,9 @@

    App version and bundle ID

    This is the version number of your app.

    - +
    -

    The recommended format for version number is 1.0.0. With a maximum value of 99 per number.

    +

    The required format for version number is x.x.x, where x is a number, e.g. 1.0.0.

    @@ -1377,20 +1562,22 @@

    App version and bundle ID

    This is a unique ID for your app on the App Store. Fliplet auto-generated one for you.

    - Change + Change
    - +

    This value needs to be unique and the structure must be com.organizationName.appName.

    + +
    @@ -1399,9 +1586,13 @@

    App version and bundle ID

    Your progress was saved successfully!

    + {{#if appFeatures.appStores.apple.private }}

    Submit your information when you are ready!
    We will take care of the rest.

    -

    Your request was sent successfully!

    + {{else}} +

    Upgrade to an app plan to request your app to be published.

    + + {{/if}}
    @@ -1412,7 +1603,7 @@

    App version and bundle ID

    Unsigned apps will need to be signed before they can be distributed. If you would like Fliplet to sign the app for you, please request an app via the "App Store" or "Enterprise" tabs above. If you request an unsigned app file, you will need a Mac or software that can resign this app for you. For more information about signing apps, please see this Apple Developer article.

    -
    +
    @@ -1441,6 +1633,7 @@
    @@ -1454,23 +1647,68 @@
    +
    @@ -1514,14 +1754,18 @@
    + {{#if organizationIsPaying }}

    Submit your information when you are ready!
    We will take care of the rest.

    -

    Your request was sent successfully!

    + {{else}} +

    Upgrade to an app plan to request your app to be published.

    + + {{/if}}
    -
    +

    You need to publish your app for these push notifications settings to be applied to your app.

    @@ -1533,19 +1777,26 @@
    +
    +
    + +

    Ensure you are logged in to your Developer account to test configuration. Login here

    +
    +
    + +

    +
    +

    -

    After saving, send notifications from the "Send push notifications" tab on the left.

    +

    After saving, send notifications from the "Send notifications" button on the Publish screen.

    Save

    Your data saved successfully!

    @@ -1611,8 +1872,13 @@

    App submission status

    Completed
    \{{#if fileUrl}}
    Download file
    \{{/if}} + \{{#if isAdmin}} \{{#if debugFileUrl}} -
    Download debug app
    \{{/if}} +
    Admin Download debug app
    + \{{else}} +
    Admin Debug app not available
    + \{{/if}} + \{{/if}} \{{#if updatedAt}} Finished on: \{{updatedAt}}\{{/if}} @@ -1621,15 +1887,20 @@

    App submission status

    \{{/if}} \{{#if ready-for-testing}} -
    Ready for testing
    +
    \{{testingStatus}}
    \{{#if fileUrl}}
    Download file
    \{{/if}} + \{{#if isAdmin}} \{{#if debugFileUrl}} -
    Download debug app
    \{{/if}} +
    Admin Download debug app
    + \{{else}} +
    Admin Debug app not available
    + \{{/if}} + \{{/if}} \{{#if updatedAt}} Ready on: \{{updatedAt}}\{{/if}} -
    App ready for testing
    +
    \{{testingMessage}}
    \{{/if}} \{{#if tested}} @@ -1644,13 +1915,26 @@

    App submission status

    \{{#if failed}}
    Failed
    -
    Try again
    \{{#if updatedAt}} Finished on: \{{updatedAt}}\{{/if}} -
    There was an error building your app
    + \{{#if message}} +
    \{{{message}}}
    + \{{else}} +
    There was an error building your app
    + \{{/if}} + + \{{/if}} + \{{#if gracefullyFailed }} + +
    Completed
    + + + \{{#if updatedAt}} Finished on: \{{updatedAt}}\{{/if}} +
    \{{{message}}}
    - \{{/if}} \{{#if cancelled}} + \{{/if}} + \{{#if cancelled}}
    Canceled
    @@ -1660,11 +1944,11 @@

    App submission status

    \{{/if}} -
    build #\{{id}}
    +
    build #\{{id}}
    \{{versionNumber}}
    \{{/each}} - \ No newline at end of file + diff --git a/js/apnValidation.js b/js/apnValidation.js new file mode 100644 index 0000000..f8cf3d6 --- /dev/null +++ b/js/apnValidation.js @@ -0,0 +1,123 @@ + +const authKeyInput = document.getElementById('fl-push-authKey'); +const keyIdInput = document.getElementById('fl-push-keyId'); +const teamIdInput = document.getElementById('fl-store-teams'); +const bundleIdInput = document.getElementById('fl-store-bundleId'); + +const testConfigButton = document.getElementById('fl-push-testConfigButton'); +const testResultMessage = document.getElementById('fl-push-testResultMessage'); + +const MESSAGE = { + SUCCESS: 'Success! Push notifications have been configured successfully.', + ERROR_INPUT: 'Error - notifications have not beeen configured, please check the details you have entered and try again', + ERROR_SERVER: 'Error - notifications have not been configured correctly. Please review https://help.fliplet.com or contact support.', + ERROR_NO_KEY: 'Authentication key is missing, please check provided information and try again', + ERROR_NO_KEYID: 'Key ID is missing, please check provided information and try again', + ERROR_NO_TEAMID: 'Developer team is missing, please check selected team in section: App Store > 5 - App technical details, and try again', + ERROR_NO_BUNDLEID: 'Bundle ID is missing, please check Bundle ID: App Store > 5 - App technical details, and try again', + ERROR_SERVICE: 'Error - There is currently an issue relating to APNs services. Please try again later.' +}; + +const mappedMessages = { + BadDeviceToken: MESSAGE.SUCCESS, + ExpiredProviderToken: MESSAGE.ERROR_INPUT, + InvalidProviderToken: MESSAGE.ERROR_INPUT, + TooManyRequests: MESSAGE.ERROR_SERVICE, + InternalServerError: MESSAGE.ERROR_SERVICE, + ServiceUnavailable: MESSAGE.ERROR_SERVICE, + Shutdown: MESSAGE.ERROR_SERVICE +}; + +const renderResultMessage = (resultMessage) => { + if (!resultMessage) { + testResultMessage.style.display = 'none'; + testResultMessage.innerHTML = ''; + + return; + } + + testResultMessage.style.display = 'block'; + testResultMessage.innerHTML = resultMessage; + + const success = resultMessage === MESSAGE.SUCCESS; + + testResultMessage.classList.toggle('text-success', success); + testResultMessage.classList.toggle('text-danger', !success); +}; + +const validateApnKey = async({ apnAuthKey, apnKeyId, apnTeamId, apnTopic }) => { + const currentAppId = Fliplet.Env.get('appId'); + + try { + const response = await fetch(`/v1/apps/${currentAppId}/notifications/validate-apns-config`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ apnAuthKey, apnKeyId, apnTeamId, apnTopic }) + }); + + if (response.status === 200) { + const { 0: { message: { 0: { errorMsg } } } } = await (response.json()); + + const message = mappedMessages[errorMsg]; + + if (message) { + return message; + } + } + + return MESSAGE.ERROR_SERVER; + } catch (error) { + // eslint-disable-next-line no-console + console.error('Failed to send message:', error); + + return MESSAGE.ERROR; + } +}; + +testConfigButton.addEventListener('click', async() => { + renderResultMessage(null); + + let resultMessage = null; + + const apnAuthKey = authKeyInput.value; + const apnKeyId = keyIdInput.value; + const apnTeamId = teamIdInput.value; + const apnTopic = bundleIdInput.value; + + + if (!apnAuthKey) { + resultMessage = MESSAGE.ERROR_NO_KEY; + } else if (!apnKeyId) { + resultMessage = MESSAGE.ERROR_NO_KEYID; + } else if (!apnTeamId) { + resultMessage = MESSAGE.ERROR_NO_TEAMID; + } else if (!apnTopic) { + resultMessage = MESSAGE.ERROR_NO_BUNDLEID; + } else { + resultMessage = await validateApnKey({ apnAuthKey, apnKeyId, apnTeamId, apnTopic }); + } + + renderResultMessage(resultMessage); +}); + +const clearMessage = () => { + renderResultMessage(null); +}; + +authKeyInput.addEventListener('input', clearMessage); +keyIdInput.addEventListener('input', clearMessage); +teamIdInput.addEventListener('input', clearMessage); + +function goToTechDetails() { + const appStoreTab = document.querySelector('[href="#appstore-tab"]'); + const techDetailsDropdownAnchor = document.querySelector('[href="#appStoreTech"]'); + const techDetailsDropdown = document.getElementById('appStoreTech'); + + appStoreTab.click(); + + if (techDetailsDropdown.getAttribute('aria-expanded') !== 'true') { + techDetailsDropdownAnchor.click(); + } +} diff --git a/js/interface.js b/js/interface.js index 512528e..46db910 100644 --- a/js/interface.js +++ b/js/interface.js @@ -1,5 +1,8 @@ var widgetId = Fliplet.Widget.getDefaultId(); var widgetData = Fliplet.Widget.getData(widgetId) || {}; +var organizationIsPaying = widgetData.organizationIsPaying; +var mustReviewTos = widgetData.mustReviewTos; +var storeFeatures = _.get(widgetData, 'appFeatures.appStores.apple', {}); var appName = ''; var organizationName = ''; var appIcon = ''; @@ -11,50 +14,176 @@ var appStoreCertificateCreated = false; var appStoreCertificateReplaced = false; var enterpriseCertificateCreated = false; var enterpriseCertificateReplaced = false; -var appStorePreviousCredential = undefined; -var appStoreFileField = undefined; -var appStoreTeamId = undefined; -var enterprisePreviousCredential = undefined; -var enterpriseFileField = undefined; -var enterpriseFileFieldManual = undefined; -var enterpriseFileProvisionFieldManual = undefined; -var enterpriseTeamId = undefined; +var previousAppStoreSubmission = {}; +var previousEnterpriseStoreSubmission = {}; +var appStorePreviousCredential; +var appStoreFileField; +var defaultReleaseNotes; +var defaultReviewNotes; +var appStoreFirebaseFileField; +var enterpriseFirebaseFileField; +var unsignedFirebaseFileField; +var enterprisePreviousCredential; +var enterpriseFileField; +var enterpriseFileFieldManual; +var enterpriseFileProvisionFieldManual; +var enterpriseTeamId; var enterpriseManual = false; +var appStoreSubmissionInStore = false; var appStoreSubmission = {}; var enterpriseSubmission = {}; var unsignedSubmission = {}; var notificationSettings = {}; var appInfo; +var demoUser; +var userInput = false; +var autoFill = 0; var statusTableTemplate = $('#status-table-template').html(); var $statusAppStoreTableElement = $('.app-build-appstore-status-holder'); var $statusEnterpriseTableElement = $('.app-build-enterprise-status-holder'); var $statusUnsignedTableElement = $('.app-build-unsigned-status-holder'); +var $pushConfigurationSaveButton = $('#pushConfiguration .btn-primary'); var initLoad; -var organizationID = Fliplet.Env.get('organizationId'); +var organizationId = Fliplet.Env.get('organizationId'); var userInfo; var hasFolders = false; -var screenShotsMobile = []; -var screenShotsTablet = []; -var haveScreenshots = false; +// Each collection requirement will need to match at least one of the width/height sizes +var screenshotRequirements = [ + { + type: 'mobile', + name: 'iPhone 5.5-inch', + sizes: [[1242, 2208]], + screenshots: [] + }, + { + type: 'mobilex', + name: 'iPhone 6.5-inch', + sizes: [[1242, 2688]], + screenshots: [] + }, + { + type: 'tablet', + name: 'iPad Pro 12.9-inch', + sizes: [[2048, 2732], [2732, 2048]], + screenshots: [] + } +]; +var hasAllScreenshots = false; var screenshotValidationNotRequired = false; +var spinner = ''; + +var formInputSelectors = [ + '#appStoreConfiguration :input', + '#enterpriseConfiguration :input', + '#unsignedConfiguration :input', + '#pushConfiguration :input' +]; + +var socketRequiresLogin = true; +var socket = Fliplet.Socket({ + login: socketRequiresLogin +}); +var socketClientId; -/* FUNCTIONS */ -String.prototype.toCamelCase = function() { - return this.replace(/^([A-Z])|[^A-Za-z]+(\w)/g, function(match, p1, p2, offset) { - if (p2) return p2.toUpperCase(); - return p1.toLowerCase(); - }).replace(/([^A-Z-a-z])/g, '').toLowerCase(); +var urlRegex = new RegExp('(https?|ftp):\/\/[^\s]+'); +var yearRegex = new RegExp('(^|[^A-Za-z0-9]{1})' + new Date().getFullYear() + '([^A-Za-z0-9]{1}|$)'); + +/* ERROR MESSAGES */ + +var ERRORS = { + INVALID_VERSION: 'The version number is incorrect. Please use a 3-part version number such as 1.0.0 where each part is no larger than 99.' +}; + +// Mapping of legacy categories and new categories +var legacyAppStoreCategories = { + Book: 'BOOKS', + Business: 'BUSINESS', + Education: 'EDUCATION', + Entertainment: 'ENTERTAINMENT', + Finance: 'FINANCE', + 'Apps.Food_Drink': 'FOOD_AND_DRINK', + Games: 'GAMES', + Healthcare_Fitness: 'HEALTH_AND_FITNESS', + Lifestyle: 'LIFESTYLE', + 'Apps.Newsstand': 'MAGAZINES_AND_NEWSPAPERS', + Medical: 'MEDICAL', + Music: 'MUSIC', + Navigation: 'NAVIGATION', + News: 'NEWS', + Photography: 'PHOTO_AND_VIDEO', + Productivity: 'PRODUCTIVITY', + Reference: 'REFERENCE', + 'Apps.Shopping': 'SHOPPING', + SocialNetworking: 'SOCIAL_NETWORKING', + Sports: 'SPORTS', + Travel: 'TRAVEL', + Utilities: 'UTILITIES' }; -var createBundleID = function(orgName, appName) { +/* FUNCTIONS */ + +function socketIsReady() { + return socket.connected && (!socketRequiresLogin || socket.loggedIn); +} + +function waitForSocketConnection() { + if (socketIsReady()) { + return Promise.resolve(); + } + + var interval; + + return new Promise(function(resolve) { + interval = setInterval(function() { + if (!socketIsReady()) { + return; + } + + clearInterval(interval); + resolve(); + }, 200); + }); +} + +function createBundleId(bundleId) { return $.ajax({ - url: "https://itunes.apple.com/lookup?bundleId=com." + orgName + "." + appName, - dataType: "jsonp" + url: 'https://itunes.apple.com/lookup?bundleId=' + bundleId, + dataType: 'jsonp' }); -}; +} + +function saveFirebaseSettings(origin) { + var formData; + + if (origin === 'appStore' && appStoreFirebaseFileField && appStoreFirebaseFileField.files[0]) { + formData = new FormData(); + + formData.append('firebase', appStoreFirebaseFileField.files[0]); + + return setFirebaseConfigFile(appStoreSubmission.id, formData); + } + + if (origin === 'enterprise' && enterpriseFirebaseFileField && enterpriseFirebaseFileField.files[0]) { + formData = new FormData(); + + formData.append('firebase', enterpriseFirebaseFileField.files[0]); + + return setFirebaseConfigFile(enterpriseSubmission.id, formData); + } + + if (origin === 'unsigned' && unsignedFirebaseFileField && unsignedFirebaseFileField.files[0]) { + formData = new FormData(); + + formData.append('firebase', unsignedFirebaseFileField.files[0]); + + return setFirebaseConfigFile(unsignedSubmission.id, formData); + } + + return Promise.resolve(); +} function incrementVersionNumber(versionNumber) { - var splitNumber = versionNumber.split('.'); + var splitNumber = _.compact(versionNumber.split('.')); var arrLength = splitNumber.length; while (arrLength--) { @@ -67,33 +196,67 @@ function incrementVersionNumber(versionNumber) { return splitNumber.join('.'); } -function checkHasScreenshots() { - haveScreenshots = hasFolders && screenShotsMobile.length && screenShotsTablet.length; +function checkHasAllScreenshots() { + hasAllScreenshots = hasFolders && _.every(screenshotRequirements, function(req) { + return req.screenshots.length; + }); + + return hasAllScreenshots; } function addThumb(thumb) { var template = Fliplet.Widget.Templates['templates.thumbs']; + return template(thumb); } +function addNoScreenshotWarning(req) { + var template = Fliplet.Widget.Templates['templates.no-thumb']; + + return template(req); +} + +function addScreenshotThumbContainers() { + var template = Fliplet.Widget.Templates['templates.thumb-containers']; + + $('.screenshot-thumb-containers').html(template(screenshotRequirements)); +} + function loadAppStoreData() { + addScreenshotThumbContainers(); $('#appStoreConfiguration [name]').each(function(i, el) { - var name = $(el).attr("name"); + var name = $(el).attr('name'); + + var hasAppId = !_.isUndefined(appStoreSubmission.data['iTunesAppId']); /* APP NAME */ - if (name === "fl-store-appName") { - $('[name="' + name + '"]').val(appName); + if (name === 'fl-store-appName') { + var storeAppName = !_.isUndefined(appStoreSubmission.data[name]) + ? appStoreSubmission.data[name] + : appName; + var maxLength = parseInt($('[name="' + name + '"]').attr('maxlength'), 10) || -1; + + if (maxLength > -1) { + storeAppName = storeAppName.substr(0, maxLength); + } + + $('[name="' + name + '"]').val(storeAppName); + + // Makes sure app store name is read-only if it's already provided + // if (!_.isUndefined($('[name="' + name + '"]').val()) && hasAppId) { + // $('[name="' + name + '"]').prop('readonly', true); + // } return; } - if (name === "fl-store-screenshots") { + if (name === 'fl-store-screenshots') { if ($('[name="' + name + '"][value="' + appStoreSubmission.data[name] + '"]:checked').length) { return; } if (appStoreSubmission.data[name]) { $('[name="' + name + '"][value="' + appStoreSubmission.data[name] + '"]').prop('checked', true).trigger('change'); - screenshotValidationNotRequired = appStoreSubmission.data[name] === 'existing' + screenshotValidationNotRequired = appStoreSubmission.data[name] === 'existing'; } else if ($('[name="' + name + '"][value="new"]:checked').length) { return; } else { @@ -104,63 +267,132 @@ function loadAppStoreData() { } /* CHECK COUNTRIES */ - if (name === "fl-store-availability") { - $('[name="' + name + '"]').selectpicker('val', ((typeof appStoreSubmission.data[name] !== "undefined") ? appStoreSubmission.data[name] : [])); + if (name === 'fl-store-availability') { + $('[name="' + name + '"]').selectpicker('val', ((typeof appStoreSubmission.data[name] !== 'undefined') ? appStoreSubmission.data[name] : [])); + return; } - if (name === "fl-store-userCountry" || name === "fl-store-category1" || name === "fl-store-category2" || name === "fl-store-language") { - $('[name="' + name + '"]').val((typeof appStoreSubmission.data[name] !== "undefined") ? appStoreSubmission.data[name] : '').trigger('change'); + + if (name === 'fl-store-userCountry' || name === 'fl-store-category1' || name === 'fl-store-category2' || name === 'fl-store-language') { + if (['fl-store-category1', 'fl-store-category2'].indexOf(name) > -1) { + var category = appStoreSubmission.data[name]; + + // Upgrade legacy app store category names + if (category && legacyAppStoreCategories[category]) { + appStoreSubmission.data[name] = legacyAppStoreCategories[category]; + } + } + + $('[name="' + name + '"]').val((typeof appStoreSubmission.data[name] !== 'undefined') ? appStoreSubmission.data[name] : '').trigger('change'); + + if (name === 'fl-store-language' && !_.isUndefined($('[name="' + name + '"]').val()) && (hasAppId || appStoreSubmissionInStore)) { + $('.dll-store-language').addClass('hidden'); + $('#fl-store-language').prop('required', false); + $('.fl-store-language-placeholder').removeClass('hidden'); + } + return; } /* ADD KEYWORDS */ - if (name === "fl-store-keywords") { - $('#' + name).tokenfield('setTokens', ((typeof appStoreSubmission.data[name] !== "undefined") ? appStoreSubmission.data[name] : '')); + if (name === 'fl-store-keywords') { + $('#' + name).tokenfield('setTokens', ((typeof appStoreSubmission.data[name] !== 'undefined') ? appStoreSubmission.data[name] : '')); } /* ADD BUNDLE ID */ - if (name === "fl-store-bundleId" && typeof appStoreSubmission.data[name] === "undefined") { - createBundleID(organizationName.toCamelCase(), appName.toCamelCase()).then(function(response) { + if (name === 'fl-store-bundleId' && typeof appStoreSubmission.data[name] === 'undefined') { + var bundleId = 'com.' + _.camelCase(organizationName) + '.' + _.camelCase(appName); + + createBundleId(bundleId).then(function(response) { if (response.resultCount === 0) { - $('.bundleId-ast-text').html('com.' + organizationName.toCamelCase() + '.' + appName.toCamelCase()); - $('[name="' + name + '"]').val('com.' + organizationName.toCamelCase() + '.' + appName.toCamelCase()); + $('.bundleId-ast-text').html(bundleId); + $('[name="' + name + '"]').val(bundleId); } else { - $('.bundleId-ast-text').html('com.' + organizationName.toCamelCase() + '.' + appName.toCamelCase() + (response.resultCount + 1)); - $('[name="' + name + '"]').val('com.' + organizationName.toCamelCase() + '.' + appName.toCamelCase() + (response.resultCount + 1)); + $('.bundleId-ast-text').html(bundleId + (response.resultCount + 1)); + $('[name="' + name + '"]').val(bundleId + (response.resultCount + 1)); } }); + return; } - if (name === "fl-store-bundleId" && typeof appStoreSubmission.data[name] !== "undefined") { + + if (name === 'fl-store-bundleId' && typeof appStoreSubmission.data[name] !== 'undefined') { $('.bundleId-ast-text').html(appStoreSubmission.data[name]); $('[name="' + name + '"]').val(appStoreSubmission.data[name]); + return; } - if (name === "fl-store-distribution") { + if (name === 'fl-store-distribution') { if (appStoreSubmission.data[name]) { $('[name="' + name + '"][value="' + appStoreSubmission.data[name] + '"]').prop('checked', true).trigger('change'); } else { $('[name="' + name + '"][value="generate-file"]').prop('checked', true).trigger('change'); } + return; } - if (name === "fl-store-versionNumber") { + if (name === 'fl-store-versionNumber') { if (typeof appStoreSubmission.data[name] !== 'undefined' && appStoreSubmission.data[name] !== '') { $('[name="' + name + '"]').val(appStoreSubmission.data[name]); } else if (typeof appStoreSubmission.previousResults !== 'undefined' && typeof appStoreSubmission.previousResults.versionNumber !== 'undefined' && appStoreSubmission.previousResults.versionNumber !== '') { $('[name="' + name + '"]').val(appStoreSubmission.previousResults.versionNumber); + $('[name="fl-store-versionNumber"]').data('validation-version-number', appStoreSubmission.previousResults.versionNumber); } else { $('[name="' + name + '"]').val('1.0.0'); } + + return; + } + + // Firebase + if (name === 'fl-store-firebase') { + return; + } + + /* Manual release */ + if (name === 'fl-store-manualRelease') { + if (!_.isUndefined(appStoreSubmission.data[name])) { + $('#' + name).prop('checked', appStoreSubmission.data[name]); + } + + return; + } + + /* Review notes */ + if (name === 'fl-store-revNotes') { + defaultReviewNotes = $('[name="' + name + '"]').val(); + + // Avoid resetting to empty string since this field has a default value + if (appStoreSubmission.data[name]) { + $('[name="' + name + '"]').val(appStoreSubmission.data[name]); + } + + return; + } + + if (name === 'fl-store-releaseNotes') { + defaultReleaseNotes = $('[name="' + name + '"]').val(); + + // Avoid resetting to empty string since this field has a default value + if (appStoreSubmission.data[name]) { + $('[name="' + name + '"]').val(appStoreSubmission.data[name]); + } + return; } - $('[name="' + name + '"]').val((typeof appStoreSubmission.data[name] !== "undefined") ? appStoreSubmission.data[name] : ''); + $('[name="' + name + '"]').val((typeof appStoreSubmission.data[name] !== 'undefined') ? appStoreSubmission.data[name] : ''); }); - if (appName !== '' && appIcon && ((hasFolders && screenShotsMobile.length && screenShotsTablet.length) || screenshotValidationNotRequired)) { + // Saving 'demo user' value from API to compare it in checkDemoUser function + demoUser = appStoreSubmission.data['fl-store-revDemoUser']; + + // When all data is loaded we can check if demo user was saved before + checkDemoUser(); + + if (appName !== '' && appIcon && (checkHasAllScreenshots() || screenshotValidationNotRequired)) { if (appSettings.splashScreen && appSettings.splashScreen.size && (appSettings.splashScreen.size[0] && appSettings.splashScreen.size[1]) < 2732) { $('.app-details-appStore .app-splash-screen').addClass('has-warning'); } @@ -169,85 +401,199 @@ function loadAppStoreData() { allAppData.push('appStore'); } else { - $('.app-details-appStore').addClass('required-fill'); - if (appName === '') { $('.app-details-appStore .app-list-name').addClass('has-error'); } + if (!appIcon) { $('.app-details-appStore .app-icon-name').addClass('has-error'); } + if (appSettings.splashScreen && appSettings.splashScreen.size && (appSettings.splashScreen.size[0] && appSettings.splashScreen.size[1]) < 2732) { $('.app-details-appStore .app-splash-screen').addClass('has-warning'); } - if (hasFolders) { - if (screenShotsMobile.length == 0 || screenShotsTablet.length == 0) { - $('.app-details-appStore .app-screenshots').addClass('has-error'); - } - } else { + if ($('[name="fl-store-screenshots"]:checked').val() === 'new' + && (!hasFolders || _.some(screenshotRequirements, function(req) { + return !req.screenshots.length; + })) + ) { $('.app-details-appStore .app-screenshots').addClass('has-error'); } } + + // Try to automatically login + if (appStoreSubmission.data && appStoreSubmission.data['fl-credentials']) { + // Submission data contains credential key + toggleLoginForm('enterprise', 'logging-in'); + + getCredential(appStoreSubmission.data['fl-credentials']) + .then(function(credential) { + if (credential && credential.appPassword) { + // Restore app-specific password + $('#fl-store-appPassword').val(credential.appPassword); + } + + if (!credential || !credential.email) { + // Allow users to manually log in if no email is found in credential + toggleLoginForm('app-store', 'login'); + + return; + } + + return appStoreTeamSetup(credential.email); + }) + .catch(function() { + // Allow users to manually log in if an error is encountered + toggleLoginForm('app-store', 'login'); + }); + } } -function loadEnterpriseData() { +function clearAppStoreCredentials() { + return setCredentials(appStoreSubmission.id, { + email: null, + password: null, + teamId: null + }, false) + .then(function() { + appStoreLoggedIn = false; + toggleLoginForm('app-store', 'login'); + }); +} + +function loadAppStoreTeams(devEmail) { + // We're avoiding making both calls in one go with Promise.all() to avoid 2FA requests being received twice + return getTeams(appStoreSubmission.id, true) + .then(function(itunesTeams) { + return Promise.all([ + Promise.resolve(itunesTeams), + getTeams(appStoreSubmission.id, false) + ]); + }) + .then(function(teams) { + var itunesTeams = teams[0]; + var appStoreTeams = teams[1]; + + appStoreTeams = _.filter(appStoreTeams, function(team) { + var itunesTeam = _.find(itunesTeams, function(itcTeam) { + return itcTeam.team_name === team.name; + }); + + if (!itunesTeam) { + return false; + } + + return team.type !== 'In-House'; + }); + + var options = ['']; + + appStoreTeams.forEach(function(team) { + options.push(''); + }); + $('#fl-load-store-teams').hide(); + $('#fl-store-teams').html(options.join('')).parent().show(); + + appStoreLoggedIn = true; + + var teamId = $('#fl-store-teams').val(); + var teamName = teamId ? $('#fl-store-teams').find(':selected').data('team-name') : ''; + + if (teamId) { + $('.appStore-more-options').addClass('show'); + } else { + $('.appStore-more-options').removeClass('show'); + } + + return refreshAppStoreOptions(devEmail, teamId, teamName); + }); +} + +function appStoreTeamSetup(email, loadTeams) { + var load = Promise.resolve(); + + if (loadTeams) { + load = loadAppStoreTeams(email); + } + return load.then(function() { + toggleLoginForm('app-store', 'logged-in', { email: email }); + }); +} + +function loadEnterpriseData() { $('#enterpriseConfiguration [name]').each(function(i, el) { - var name = $(el).attr("name"); + var name = $(el).attr('name'); /* ADD BUNDLE ID */ - if (name === "fl-ent-bundleId" && typeof enterpriseSubmission.data[name] === "undefined") { - createBundleID(organizationName.toCamelCase(), appName.toCamelCase()).then(function(response) { + if (name === 'fl-ent-bundleId' && typeof enterpriseSubmission.data[name] === 'undefined') { + var bundleId = 'com.' + _.camelCase(organizationName) + '.' + _.camelCase(appName); + + createBundleId(bundleId).then(function(response) { if (response.resultCount === 0) { - $('.bundleId-ent-text').html('com.' + organizationName.toCamelCase() + '.' + appName.toCamelCase()); - $('[name="' + name + '"]').val('com.' + organizationName.toCamelCase() + '.' + appName.toCamelCase()); + $('.bundleId-ent-text').html(bundleId); + $('[name="' + name + '"]').val(bundleId); } else { - $('.bundleId-ent-text').html('com.' + organizationName.toCamelCase() + '.' + appName.toCamelCase() + (response.resultCount + 1)); - $('[name="' + name + '"]').val('com.' + organizationName.toCamelCase() + '.' + appName.toCamelCase() + (response.resultCount + 1)); + $('.bundleId-ent-text').html(bundleId + (response.resultCount + 1)); + $('[name="' + name + '"]').val(bundleId + (response.resultCount + 1)); } }); + return; } - if (name === "fl-ent-bundleId" && typeof enterpriseSubmission.data[name] !== "undefined") { + + if (name === 'fl-ent-bundleId' && typeof enterpriseSubmission.data[name] !== 'undefined') { $('.bundleId-ent-text').html(enterpriseSubmission.data[name]); $('[name="' + name + '"]').val(enterpriseSubmission.data[name]); + return; } - if (name === "fl-ent-versionNumber") { + + if (name === 'fl-ent-versionNumber') { if (typeof enterpriseSubmission.data[name] !== 'undefined' && enterpriseSubmission.data[name] !== '') { $('[name="' + name + '"]').val(enterpriseSubmission.data[name]); } else if (typeof enterpriseSubmission.previousResults !== 'undefined' && typeof enterpriseSubmission.previousResults.versionNumber !== 'undefined' && enterpriseSubmission.previousResults.versionNumber !== '') { $('[name="' + name + '"]').val(enterpriseSubmission.previousResults.versionNumber); + $('[name="fl-ent-versionNumber"]').data('validation-version-number', enterpriseSubmission.previousResults.versionNumber); } else { $('[name="' + name + '"]').val('1.0.0'); } + return; } - if (name === "fl-ent-distribution") { + if (name === 'fl-ent-distribution') { if (enterpriseSubmission.data[name]) { $('[name="' + name + '"][value="' + enterpriseSubmission.data[name] + '"]').prop('checked', true).trigger('change'); } else { $('[name="' + name + '"][value="generate-file"]').prop('checked', true).trigger('change'); } + return; } - if (name === "fl-ent-certificate-manual-details" || name === "fl-ent-mobileprovision-manual-details") { + if (name === 'fl-ent-certificate-manual-details' || name === 'fl-ent-mobileprovision-manual-details') { return; } - $('[name="' + name + '"]').val((typeof enterpriseSubmission.data[name] !== "undefined") ? enterpriseSubmission.data[name] : ''); + // Firebase + if (name === 'fl-ent-firebase') { + return; + } + + $('[name="' + name + '"]').val((typeof enterpriseSubmission.data[name] !== 'undefined') ? enterpriseSubmission.data[name] : ''); }); if (appIcon) { if (appSettings.splashScreen && appSettings.splashScreen.size && (appSettings.splashScreen.size[0] && appSettings.splashScreen.size[1]) < 2732) { $('.app-details-ent .app-splash-screen').addClass('has-warning'); } + if (appSettings.iconData && appSettings.iconData.size && (appSettings.iconData.size[0] && appSettings.iconData.size[1]) < 1024) { $('.app-details-ent .app-icon-name').addClass('has-error'); } + allAppData.push('enterprise'); } else { $('.app-details-ent').addClass('required-fill'); @@ -255,56 +601,143 @@ function loadEnterpriseData() { if (!appIcon) { $('.app-details-ent .app-icon-name').addClass('has-error'); } + if (appSettings.splashScreen && appSettings.splashScreen.size && (appSettings.splashScreen.size[0] && appSettings.splashScreen.size[1]) < 2732) { $('.app-details-ent .app-splash-screen').addClass('has-warning'); } } + + // Try to automatically login + if (enterpriseSubmission.data && enterpriseSubmission.data['fl-credentials']) { + // Submission data contains credential key + toggleLoginForm('enterprise', 'logging-in'); + + getCredential(enterpriseSubmission.data['fl-credentials']).then(function(credential) { + if (!credential || !credential.email) { + toggleLoginForm('enterprise', 'login'); + + return; + } + + return enterpriseTeamSetup(credential.email); + }).catch(function() { + // Allow users to manually log in if an error is encountered + toggleLoginForm('enterprise', 'login'); + }); + } } -function loadUnsignedData() { +function clearEnterpriseCredentials() { + return setCredentials(enterpriseSubmission.id, { + email: null, + password: null, + teamId: null + }, false) + .then(function() { + enterpriseLoggedIn = false; + toggleLoginForm('enterprise', 'login'); + }); +} + +function loadEnterpriseTeams(devEmail) { + return getTeams(enterpriseSubmission.id, false) + .then(function(teams) { + var enterpriseTeams = _.filter(teams, function(team) { + return team.type === 'In-House'; + }); + var options = ['']; + + enterpriseTeams.forEach(function(team) { + options.push(''); + }); + $('#fl-load-ent-teams').hide(); + $('#fl-ent-teams').html(options.join('')).parent().show(); + + enterpriseLoggedIn = true; + var teamId = $('#fl-ent-teams').val(); + var teamName = teamId ? $('#fl-ent-teams').find(':selected').data('team-name') : ''; + + if (teamId) { + $('.enterprise-more-options').addClass('show'); + } else { + $('.enterprise-more-options').removeClass('show'); + } + + return refreshAppEnterpriseOptions(devEmail, teamId, teamName); + }); +} + +function enterpriseTeamSetup(email, loadTeams) { + var load = Promise.resolve(); + + if (loadTeams) { + load = loadEnterpriseTeams(email); + } + + return load.then(function() { + toggleLoginForm('enterprise', 'logged-in', { email: email }); + }); +} + +function loadUnsignedData() { $('#unsignedConfiguration [name]').each(function(i, el) { - var name = $(el).attr("name"); + var name = $(el).attr('name'); /* ADD BUNDLE ID */ - if (name === "fl-uns-bundleId" && typeof unsignedSubmission.data[name] === "undefined") { - createBundleID(organizationName.toCamelCase(), appName.toCamelCase()).then(function(response) { + if (name === 'fl-uns-bundleId' && typeof unsignedSubmission.data[name] === 'undefined') { + var bundleId = 'com.' + _.camelCase(organizationName) + '.' + _.camelCase(appName); + + createBundleId(bundleId).then(function(response) { if (response.resultCount === 0) { - $('.bundleId-uns-text').html('com.' + organizationName.toCamelCase() + '.' + appName.toCamelCase()); - $('[name="' + name + '"]').val('com.' + organizationName.toCamelCase() + '.' + appName.toCamelCase()); + $('.bundleId-uns-text').html(bundleId); + $('[name="' + name + '"]').val(bundleId); } else { - $('.bundleId-uns-text').html('com.' + organizationName.toCamelCase() + '.' + appName.toCamelCase() + (response.resultCount + 1)); - $('[name="' + name + '"]').val('com.' + organizationName.toCamelCase() + '.' + appName.toCamelCase() + (response.resultCount + 1)); + $('.bundleId-uns-text').html(bundleId + (response.resultCount + 1)); + $('[name="' + name + '"]').val(bundleId + (response.resultCount + 1)); } }); + return; } - if (name === "fl-uns-bundleId" && typeof unsignedSubmission.data[name] !== "undefined") { + + if (name === 'fl-uns-bundleId' && typeof unsignedSubmission.data[name] !== 'undefined') { $('.bundleId-uns-text').html(unsignedSubmission.data[name]); $('[name="' + name + '"]').val(unsignedSubmission.data[name]); + return; } - if (name === "fl-uns-versionNumber") { + + if (name === 'fl-uns-versionNumber') { if (typeof unsignedSubmission.data[name] !== 'undefined' && unsignedSubmission.data[name] !== '') { $('[name="' + name + '"]').val(unsignedSubmission.data[name]); } else if (typeof unsignedSubmission.previousResults !== 'undefined' && typeof unsignedSubmission.previousResults.versionNumber !== 'undefined' && unsignedSubmission.previousResults.versionNumber !== '') { $('[name="' + name + '"]').val(unsignedSubmission.previousResults.versionNumber); + $('[name="fl-uns-versionNumber"]').data('validation-version-number', unsignedSubmission.previousResults.versionNumber); } else { $('[name="' + name + '"]').val('1.0.0'); } + return; } - $('[name="' + name + '"]').val((typeof unsignedSubmission.data[name] !== "undefined") ? unsignedSubmission.data[name] : ''); + // Firebase + if (name === 'fl-uns-firebase') { + return; + } + + $('[name="' + name + '"]').val((typeof unsignedSubmission.data[name] !== 'undefined') ? unsignedSubmission.data[name] : ''); }); if (appIcon) { if (appSettings.splashScreen && appSettings.splashScreen.size && (appSettings.splashScreen.size[0] && appSettings.splashScreen.size[1]) < 2732) { $('.app-details-uns .app-splash-screen').addClass('has-warning'); } + if (appSettings.iconData && appSettings.iconData.size && (appSettings.iconData.size[0] && appSettings.iconData.size[1]) < 1024) { $('.app-details-uns .app-icon-name').addClass('has-error'); } + allAppData.push('unsigned'); } else { $('.app-details-uns').addClass('required-fill'); @@ -312,219 +745,294 @@ function loadUnsignedData() { if (!appIcon) { $('.app-details-uns .app-icon-name').addClass('has-error'); } + if (appSettings.splashScreen && appSettings.splashScreen.size && (appSettings.splashScreen.size[0] && appSettings.splashScreen.size[1]) < 2732) { $('.app-details-uns .app-splash-screen').addClass('has-warning'); } } } - function loadPushNotesData() { $('#pushConfiguration [name]').each(function(i, el) { - var name = $(el).attr("name"); + var name = $(el).attr('name'); // ADDING NOTIFICATIONS SETTINGS if (name === 'fl-push-authKey') { $('[name="' + name + '"]').val(notificationSettings.apnAuthKey || ''); + return; } + if (name === 'fl-push-keyId') { $('[name="' + name + '"]').val(notificationSettings.apnKeyId || ''); + return; } }); } function submissionBuild(appSubmission, origin) { - Fliplet.App.Submissions.build(appSubmission.id).then(function(builtSubmission) { + var newVersionNumber; - if (origin === "appStore") { + saveFirebaseSettings(origin).then(function() { + return Fliplet.App.Submissions.build(appSubmission.id); + }).then(function(builtSubmission) { + if (origin === 'appStore') { appStoreSubmission = builtSubmission.submission; + // Auto increments the version number and saves the submission - var newVersionNumber = incrementVersionNumber(appStoreSubmission.data['fl-store-versionNumber']); - $('[name="fl-store-versionNumber"]').val(newVersionNumber); + newVersionNumber = incrementVersionNumber(appStoreSubmission.data['fl-store-versionNumber']); - $('.appStore-login-details').removeClass('hidden'); - $('.appStore-logged-in, .appStore-more-options, .appStore-teams').removeClass('show'); - appStoreLoggedIn = false; + $('[name="fl-store-versionNumber"]').val(newVersionNumber); saveAppStoreData(); + $('#fl-store-teams').val(''); + $('.appStore-more-options').removeClass('show'); } - if (origin === "enterprise") { + + if (origin === 'enterprise') { enterpriseSubmission = builtSubmission.submission; + // Auto increments the version number and saves the submission - var newVersionNumber = incrementVersionNumber(enterpriseSubmission.data['fl-ent-versionNumber']); - $('[name="fl-ent-versionNumber"]').val(newVersionNumber); + newVersionNumber = incrementVersionNumber(enterpriseSubmission.data['fl-ent-versionNumber']); - $('.enterprise-login-details').removeClass('hidden'); - $('.enterprise-logged-in, .enterprise-more-options, .enterprise-teams').removeClass('show'); - enterpriseLoggedIn = false; + $('[name="fl-ent-versionNumber"]').val(newVersionNumber); saveEnterpriseData(); + $('#fl-ent-teams').val(''); + $('.enterprise-more-options').removeClass('show'); } - if (origin === "unsigned") { + + if (origin === 'unsigned') { unsignedSubmission = builtSubmission.submission; + // Auto increments the version number and saves the submission - var newVersionNumber = incrementVersionNumber(unsignedSubmission.data['fl-uns-versionNumber']); + newVersionNumber = incrementVersionNumber(unsignedSubmission.data['fl-uns-versionNumber']); + $('[name="fl-uns-versionNumber"]').val(newVersionNumber); saveUnsignedData(); } Fliplet.Studio.emit('refresh-app-submissions'); + Fliplet.Studio.emit('app-launch', { + platform: 'ios', + submissionType: origin + }); + + Fliplet.Modal.alert({ + title: 'Your request was sent successfully!', + message: 'Your app is building!' + }).then(function() { + document.getElementById('nav-tabs').scrollIntoView({ behavior: 'smooth', block: 'start' }); + }); $('.button-' + origin + '-request').html('Request App '); - $('.save-' + origin + '-request').addClass('saved').hide().fadeIn(250); + $('.button-' + origin + '-request').prop('disabled', false); clearTimeout(initLoad); initialLoad(false, 0); Fliplet.Widget.autosize(); - - setTimeout(function() { - $('.save-' + origin + '-request').fadeOut(250, function() { - $('.save-' + origin + '-request').removeClass('saved'); - Fliplet.Widget.autosize(); - }); - }, 10000); }, function(err) { $('.button-' + origin + '-request').html('Request App '); - alert(err.responseJSON.message); + $('.button-' + origin + '-request').prop('disabled', false); + + if (Fliplet.Error.isHandled(err)) { + return; + } + + Fliplet.Modal.alert({ + message: Fliplet.parseError(err) + }); }); } function save(origin, submission) { + submission = submission || {}; - Fliplet.App.Submissions.get() + return Fliplet.App.Submissions.get() .then(function(submissions) { var savedSubmission = _.find(submissions, function(sub) { return sub.id === submission.id; }); submission = _.extend(savedSubmission, submission); + return Promise.resolve(); }) .then(function() { if (submission.status !== 'started') { - if(submission.data.hasOwnProperty('fl-credentials')){ + var previousCredentials = submission.data['fl-credentials']; + + if (submission.data.hasOwnProperty('fl-credentials')) { delete submission.data['fl-credentials']; } + return Fliplet.App.Submissions.create({ - platform: 'ios', - data: $.extend(true, submission.data, { - previousResults: submission.result - }) + platform: 'ios', + data: $.extend(true, submission.data, { + previousResults: submission.result }) + }) .then(function(newSubmission) { - if (origin === "appStore") { - appStoreSubmission = newSubmission; - } - if (origin === "enterprise") { - enterpriseSubmission = newSubmission; - } - if (origin === "unsigned") { - unsignedSubmission = newSubmission; - } + var cloneCredentialsPromise = Promise.resolve(); + + newSubmission.data['fl-credentials'] = 'submission-' + newSubmission.id; + + // Before we will decide clone credentials or not we should check has it been set by this time + return getCredential(previousCredentials).then(function(credentialIsExist) { + if (origin === 'appStore' && credentialIsExist) { + appStoreSubmission = newSubmission; + cloneCredentialsPromise = cloneCredentials(previousCredentials, appStoreSubmission); + } else if (origin === 'enterprise' && credentialIsExist) { + enterpriseSubmission = newSubmission; + cloneCredentialsPromise = cloneCredentials(previousCredentials, enterpriseSubmission); + } else if (origin === 'unsigned' && credentialIsExist) { + unsignedSubmission = newSubmission; + cloneCredentialsPromise = cloneCredentials(previousCredentials, unsignedSubmission); + } - Fliplet.App.Submissions.update(newSubmission.id, newSubmission.data).then(function() { - $('.save-' + origin + '-progress').addClass('saved'); + return cloneCredentialsPromise.then(function() { + return Fliplet.App.Submissions.update(newSubmission.id, newSubmission.data); + }).then(function() { + $('.save-' + origin + '-progress').addClass('saved'); - setTimeout(function() { - $('.save-' + origin + '-progress').removeClass('saved'); - }, 4000); + setTimeout(function() { + $('.save-' + origin + '-progress').removeClass('saved'); + }, 4000); + }); }); - }); } - Fliplet.App.Submissions.update(submission.id, submission.data).then(function() { - $('.save-' + origin + '-progress').addClass('saved'); - - setTimeout(function() { - $('.save-' + origin + '-progress').removeClass('saved'); - }, 4000); + // Save app-specific password before saving remaining submission data + return saveFirebaseSettings(origin).then(function() { + return setCredentials(submission.id, { + appPassword: $('#fl-store-appPassword').val().trim() + }, false).then(function() { + return Fliplet.App.Submissions.update(submission.id, submission.data); + }).then(function() { + $('.save-' + origin + '-progress').addClass('saved'); + + setTimeout(function() { + $('.save-' + origin + '-progress').removeClass('saved'); + }, 4000); + }); }); }) .catch(function(err) { - alert(err.responseJSON.message); + Fliplet.Modal.alert({ + message: Fliplet.parseError(err) + }); + }); +} + +function checkFileExtension(fileName, element, validExt) { + var lastDotInName = fileName.lastIndexOf('.'); + var fileExt = fileName.substring(lastDotInName); + + if (fileExt !== validExt) { + Fliplet.Modal.alert({ + title: 'Wrong file extension', + message: 'Please select a ' + validExt + ' file', + size: 'small' + }).then(function() { + $(element).val(''); }); + + return false; + } + + return true; } function requestBuild(origin, submission) { - $('.button-' + origin + '-request').html('Requesting '); + $('.button-' + origin + '-request').html('Requesting ' + spinner); if (origin === 'appStore') { submission.data.folderStructure = appSettings.folderStructure; } var defaultSplashScreenData = { - "url": $('[data-' + origin.toLowerCase() + '-default-splash-url]').data(origin.toLowerCase() + '-default-splash-url') + 'url': $('[data-' + origin.toLowerCase() + '-default-splash-url]').data(origin.toLowerCase() + '-default-splash-url') }; submission.data.splashScreen = appSettings.splashScreen ? appSettings.splashScreen : defaultSplashScreenData; submission.data.appIcon = appIcon; submission.data.legacyBuild = appSettings.legacyBuild || false; - Fliplet.App.Submissions.get() + return Fliplet.App.Submissions.get() .then(function(submissions) { var savedSubmission = _.find(submissions, function(sub) { return sub.id === submission.id; }); submission = _.extend(savedSubmission, submission); + return Promise.resolve(); }) .then(function() { if (submission.status !== 'started') { - if(submission.data.hasOwnProperty('fl-credentials')){ + if (submission.data.hasOwnProperty('fl-credentials')) { delete submission.data['fl-credentials']; } + return Fliplet.App.Submissions.create({ - platform: 'ios', - data: $.extend(true, submission.data, { - previousResults: submission.result - }) + platform: 'ios', + data: $.extend(true, submission.data, { + previousResults: submission.result }) + }) .then(function(newSubmission) { - if (origin === "appStore") { + var formData; + var fileName; + var teamId; + var teamName; + + if (origin === 'appStore') { appStoreSubmission = newSubmission; } - if (origin === "enterprise") { + + if (origin === 'enterprise') { enterpriseSubmission = newSubmission; } - if (origin === "unsigned") { + + if (origin === 'unsigned') { unsignedSubmission = newSubmission; } // Check which type of certificate was given - if (origin === "appStore" && appStoreSubmission.data['fl-store-distribution'] === 'previous-file' && appStorePreviousCredential) { - return setCredentials(organizationID, appStoreSubmission.id, { - teamId: appStorePreviousCredential.teamId, - teamName: appStorePreviousCredential.teamName, - certSigningRequest: appStorePreviousCredential.certSigningRequest, - p12: appStorePreviousCredential.p12, - certificate: appStorePreviousCredential.certificate, - content: appStorePreviousCredential.content - }) + if (origin === 'appStore' && appStoreSubmission.data['fl-store-distribution'] === 'previous-file' && appStorePreviousCredential) { + return setCredentials(appStoreSubmission.id, { + teamId: appStorePreviousCredential.teamId, + teamName: appStorePreviousCredential.teamName, + certSigningRequest: appStorePreviousCredential.certSigningRequest, + p12: appStorePreviousCredential.p12, + certificate: appStorePreviousCredential.certificate, + content: appStorePreviousCredential.content + }) .then(function() { submissionBuild(newSubmission, origin); }); } - if (origin === "appStore" && appStoreSubmission.data['fl-store-distribution'] === 'upload-file') { - var formData = new FormData(); - var fileName = appStoreFileField.value.replace(/\\/g, '/').replace(/.*\//, ''); - var teamID = $('#fl-store-teams').val(); - var teamName = $('#fl-store-teams').find(":selected").data('team-name'); + // TODO: APPEND FIREBASE FILE? + + if (origin === 'appStore' && appStoreSubmission.data['fl-store-distribution'] === 'upload-file') { + formData = new FormData(); + fileName = appStoreFileField.value.replace(/\\/g, '/').replace(/.*\//, ''); + teamId = $('#fl-store-teams').val(); + teamName = $('#fl-store-teams').find(':selected').data('team-name'); if (appStoreFileField.files && appStoreFileField.files[0]) { - formData.append('p12', appStoreFileField.files[0]) - formData.append('certificateName', fileName) + formData.append('p12', appStoreFileField.files[0]); + formData.append('certificateName', fileName); } - return setCertificateP12(organizationID, appStoreSubmission.id, formData) + return setCertificateP12(appStoreSubmission.id, formData) .then(function() { - return setCredentials(organizationID, appStoreSubmission.id, { - teamId: teamID, + return setCredentials(appStoreSubmission.id, { + teamId: teamId, teamName: teamName }); }) @@ -533,42 +1041,42 @@ function requestBuild(origin, submission) { }); } - if (origin === "enterprise" && enterpriseSubmission.data['fl-ent-distribution'] === 'previous-file' && enterprisePreviousCredential) { - return setCredentials(organizationID, enterpriseSubmission.id, { - teamId: enterprisePreviousCredential.teamId, - teamName: enterprisePreviousCredential.teamName, - certSigningRequest: enterprisePreviousCredential.certSigningRequest, - p12: enterprisePreviousCredential.p12, - certificate: enterprisePreviousCredential.certificate, - content: enterprisePreviousCredential.content - }) + if (origin === 'enterprise' && enterpriseSubmission.data['fl-ent-distribution'] === 'previous-file' && enterprisePreviousCredential) { + return setCredentials(enterpriseSubmission.id, { + teamId: enterprisePreviousCredential.teamId, + teamName: enterprisePreviousCredential.teamName, + certSigningRequest: enterprisePreviousCredential.certSigningRequest, + p12: enterprisePreviousCredential.p12, + certificate: enterprisePreviousCredential.certificate, + content: enterprisePreviousCredential.content + }) .then(function() { submissionBuild(newSubmission, origin); }); } - if (origin === "enterprise" && enterpriseSubmission.data['fl-ent-distribution'] === 'upload-file') { - var formData = new FormData(); - var fileName = enterpriseFileField.value.replace(/\\/g, '/').replace(/.*\//, ''); + if (origin === 'enterprise' && enterpriseSubmission.data['fl-ent-distribution'] === 'upload-file') { + formData = new FormData(); + fileName = enterpriseFileField.value.replace(/\\/g, '/').replace(/.*\//, ''); if (enterpriseFileField.files && enterpriseFileField.files[0]) { - formData.append('p12', enterpriseFileField.files[0]) - formData.append('certificateName', fileName) + formData.append('p12', enterpriseFileField.files[0]); + formData.append('certificateName', fileName); } - var teamId = $('#fl-ent-teams').val(); - var teamName = $('#fl-ent-teams').find(":selected").data('team-name'); + teamId = $('#fl-ent-teams').val(); + teamName = $('#fl-ent-teams').find(':selected').data('team-name'); - return setCredentials(organizationID, enterpriseSubmission.id, { - teamId: teamId, - teamName: teamName - }) + return setCredentials(enterpriseSubmission.id, { + teamId: teamId, + teamName: teamName + }) .then(function() { - return setCertificateP12(organizationID, enterpriseSubmission.id, formData) + return setCertificateP12(enterpriseSubmission.id, formData); }) .then(function() { - return setCredentials(organizationID, enterpriseSubmission.id, { - teamId: teamID, + return setCredentials(enterpriseSubmission.id, { + teamId: teamId, teamName: teamName }); }) @@ -581,37 +1089,48 @@ function requestBuild(origin, submission) { }); } - Fliplet.App.Submissions.update(submission.id, submission.data).then(function() { + // Code for first submission of this type + + setCredentials(appStoreSubmission.id, { + appPassword: $('#fl-store-appPassword').val().trim() + }, false).then(function() { + return Fliplet.App.Submissions.update(submission.id, submission.data); + }).then(function() { + var formData; + var fileName; + var teamId; + var teamName; + // Check which type of certificate was given - if (origin === "appStore" && appStoreSubmission.data['fl-store-distribution'] === 'previous-file' && appStorePreviousCredential) { - return setCredentials(organizationID, appStoreSubmission.id, { - teamId: appStorePreviousCredential.teamId, - teamName: appStorePreviousCredential.teamName, - certSigningRequest: appStorePreviousCredential.certSigningRequest, - p12: appStorePreviousCredential.p12, - certificate: appStorePreviousCredential.certificate, - content: appStorePreviousCredential.content - }) + if (origin === 'appStore' && appStoreSubmission.data['fl-store-distribution'] === 'previous-file' && appStorePreviousCredential) { + return setCredentials(appStoreSubmission.id, { + teamId: appStorePreviousCredential.teamId, + teamName: appStorePreviousCredential.teamName, + certSigningRequest: appStorePreviousCredential.certSigningRequest, + p12: appStorePreviousCredential.p12, + certificate: appStorePreviousCredential.certificate, + content: appStorePreviousCredential.content + }) .then(function() { submissionBuild(submission, origin); }); } - if (origin === "appStore" && appStoreSubmission.data['fl-store-distribution'] === 'upload-file') { - var formData = new FormData(); - var fileName = appStoreFileField.value.replace(/\\/g, '/').replace(/.*\//, ''); - var teamID = $('#fl-store-teams').val(); - var teamName = $('#fl-store-teams').find(":selected").data('team-name'); + if (origin === 'appStore' && appStoreSubmission.data['fl-store-distribution'] === 'upload-file') { + formData = new FormData(); + fileName = appStoreFileField.value.replace(/\\/g, '/').replace(/.*\//, ''); + teamId = $('#fl-store-teams').val(); + teamName = $('#fl-store-teams').find(':selected').data('team-name'); if (appStoreFileField.files && appStoreFileField.files[0]) { - formData.append('p12', appStoreFileField.files[0]) - formData.append('certificateName', fileName) + formData.append('p12', appStoreFileField.files[0]); + formData.append('certificateName', fileName); } - return setCertificateP12(organizationID, appStoreSubmission.id, formData) + return setCertificateP12(appStoreSubmission.id, formData) .then(function() { - return setCredentials(organizationID, appStoreSubmission.id, { - teamId: teamID, + return setCredentials(appStoreSubmission.id, { + teamId: teamId, teamName: teamName }); }) @@ -620,35 +1139,35 @@ function requestBuild(origin, submission) { }); } - if (origin === "enterprise" && enterpriseSubmission.data['fl-ent-distribution'] === 'previous-file' && enterprisePreviousCredential) { - return setCredentials(organizationID, enterpriseSubmission.id, { - teamId: enterprisePreviousCredential.teamId, - teamName: enterprisePreviousCredential.teamName, - certSigningRequest: enterprisePreviousCredential.certSigningRequest, - p12: enterprisePreviousCredential.p12, - certificate: enterprisePreviousCredential.certificate, - content: enterprisePreviousCredential.content - }) + if (origin === 'enterprise' && enterpriseSubmission.data['fl-ent-distribution'] === 'previous-file' && enterprisePreviousCredential) { + return setCredentials(enterpriseSubmission.id, { + teamId: enterprisePreviousCredential.teamId, + teamName: enterprisePreviousCredential.teamName, + certSigningRequest: enterprisePreviousCredential.certSigningRequest, + p12: enterprisePreviousCredential.p12, + certificate: enterprisePreviousCredential.certificate, + content: enterprisePreviousCredential.content + }) .then(function() { submissionBuild(submission, origin); }); } - if (origin === "enterprise" && enterpriseSubmission.data['fl-ent-distribution'] === 'upload-file') { - var formData = new FormData(); - var fileName = enterpriseFileField.value.replace(/\\/g, '/').replace(/.*\//, ''); - var teamID = $('#fl-ent-teams').val(); - var teamName = $('#fl-ent-teams').find(":selected").data('team-name'); + if (origin === 'enterprise' && enterpriseSubmission.data['fl-ent-distribution'] === 'upload-file') { + formData = new FormData(); + fileName = enterpriseFileField.value.replace(/\\/g, '/').replace(/.*\//, ''); + teamId = $('#fl-ent-teams').val(); + teamName = $('#fl-ent-teams').find(':selected').data('team-name'); if (enterpriseFileField.files && enterpriseFileField.files[0]) { - formData.append('p12', enterpriseFileField.files[0]) - formData.append('certificateName', fileName) + formData.append('p12', enterpriseFileField.files[0]); + formData.append('certificateName', fileName); } - return setCertificateP12(organizationID, enterpriseSubmission.id, formData) + return setCertificateP12(enterpriseSubmission.id, formData) .then(function() { - return setCredentials(organizationID, enterpriseSubmission.id, { - teamId: teamID, + return setCredentials(enterpriseSubmission.id, { + teamId: teamId, teamName: teamName }); }) @@ -662,47 +1181,76 @@ function requestBuild(origin, submission) { }) .catch(function(err) { $('.button-' + origin + '-request').html('Request App '); - alert(err.responseJSON.message); + $('.button-' + origin + '-request').prop('disabled', false); + Fliplet.Modal.alert({ + message: Fliplet.parseError(err) + }); }); } function saveAppStoreData(request) { - var data = appStoreSubmission.data; + var data = appStoreSubmission.data || {}; var pushData = notificationSettings; $('#appStoreConfiguration [name]').each(function(i, el) { - var name = $(el).attr("name"); + var name = $(el).attr('name'); var value = $(el).val(); + var newValue; + + if (typeof value === 'string') { + value = value.trim(); + } + + if (name === 'fl-store-appPassword' || name === 'fl-store-firebase') { + // Skip saving app-specific password and Firebase config file + // This will be saved in credentials + return; + } /* PROCESSING KEYWORDS */ if (name === 'fl-store-keywords') { - var newValue = value.replace(/,\s+/g, ','); + newValue = value.replace(/,\s+/g, ','); + data[name] = newValue; + return; } if (name === 'fl-store-screenshots') { - var newValue = $('[name="'+name+'"]:checked').val(); + newValue = $('[name="' + name + '"]:checked').val(); + data[name] = newValue; + + return; + } + + /* Manual release */ + if (name === 'fl-store-manualRelease') { + data[name] = $('[name="' + name + '"]').is(':checked'); + return; } if (name === 'fl-store-distribution') { - var newValue = $('[name="'+name+'"]:checked').val(); - if (newValue === 'previous-file') { + newValue = $('[name="' + name + '"]:checked').val(); + + if (newValue === 'previous-file' && appStorePreviousCredential && appStorePreviousCredential.teamId) { pushData.apnTeamId = appStorePreviousCredential.teamId; } - if (newValue === 'generate-file' || newValue === 'upload-file') { + + if ((newValue === 'generate-file' || newValue === 'upload-file') && $('#fl-store-teams').val()) { pushData.apnTeamId = $('#fl-store-teams').val(); } data[name] = newValue; + return; } if (name === 'fl-store-bundleId') { pushData.apnTopic = value; data[name] = value; + return; } @@ -713,48 +1261,90 @@ function saveAppStoreData(request) { appStoreSubmission.data = data; notificationSettings = pushData; + savePushData(true); + if (request) { - requestBuild('appStore', appStoreSubmission); - } else { - save('appStore', appStoreSubmission); + if (!storeFeatures.public) { + Fliplet.Studio.emit('overlay', { + name: 'app-settings', + options: { + size: 'large', + title: 'App Settings', + appId: Fliplet.Env.get('appId'), + section: 'appBilling', + helpLink: 'https://help.fliplet.com/app-settings/' + } + }); + + Fliplet.Studio.emit('track-event', { + category: 'app_billing', + action: 'open', + context: 'apple_launch' + }); + + return; + } + + return requestBuild('appStore', appStoreSubmission); } + + return save('appStore', appStoreSubmission); } function saveEnterpriseData(request) { - var data = enterpriseSubmission.data; + var data = enterpriseSubmission.data || {}; var pushData = notificationSettings; var uploadFilePromise = Promise.resolve(); $('#enterpriseConfiguration [name]').each(function(i, el) { - var name = $(el).attr("name"); + var name = $(el).attr('name'); var value = $(el).val(); + if (typeof value === 'string') { + value = value.trim(); + } + if (name === 'fl-ent-distribution') { - var newValue = $('[name="'+name+'"]:checked').val(); - if (newValue === 'previous-file') { + var newValue = $('[name="' + name + '"]:checked').val(); + + if (newValue === 'previous-file' && enterprisePreviousCredential && enterprisePreviousCredential.teamId) { pushData.apnTeamId = enterprisePreviousCredential.teamId; } - if (newValue === 'generate-file' || newValue === 'upload-file') { + + if ((newValue === 'generate-file' || newValue === 'upload-file') && $('#fl-ent-teams').val()) { pushData.apnTeamId = $('#fl-ent-teams').val(); } + data[name] = newValue; + return; } + if (name === 'fl-ent-firebase') { + return; // saved in credentials + } + if (name === 'fl-ent-teamId') { if (enterpriseManual) { data[name] = value; - pushData.apnTeamId = enterpriseTeamId; + + if (enterpriseTeamId) { + pushData.apnTeamId = enterpriseTeamId; + } + return; } + return; } if (name === 'fl-ent-teamName') { if (enterpriseManual) { data[name] = value; + return; } + return; } @@ -762,6 +1352,7 @@ function saveEnterpriseData(request) { if (name === 'fl-ent-bundleId') { pushData.apnTopic = value; data[name] = value; + return; } @@ -769,8 +1360,8 @@ function saveEnterpriseData(request) { }); if (enterpriseManual) { - var fileList = enterpriseFileFieldManual.files - var fileProvisionList = enterpriseFileProvisionFieldManual.files + var fileList = enterpriseFileFieldManual.files; + var fileProvisionList = enterpriseFileProvisionFieldManual.files; var file = new FormData(); if (fileList.length > 0 && fileProvisionList.length > 0) { @@ -778,7 +1369,7 @@ function saveEnterpriseData(request) { file.append('fl-ent-certificate-manual-file', fileList[i]); } - for (var i = 0; i < fileProvisionList.length; i++) { + for (i = 0; i < fileProvisionList.length; i++) { file.append('fl-ent-provision-manual-file', fileProvisionList[i]); } @@ -787,6 +1378,7 @@ function saveEnterpriseData(request) { appId: Fliplet.Env.get('appId') }).then(function(files) { data['fl-ent-certificate-files'] = files; + return Promise.resolve(); }); } @@ -795,59 +1387,124 @@ function saveEnterpriseData(request) { enterpriseSubmission.data = data; notificationSettings = pushData; + delete enterpriseSubmission.data['fl-credentials']; + + savePushData(true); + if (request) { - requestBuild('enterprise', enterpriseSubmission); - } else { - save('enterprise', enterpriseSubmission); + return requestBuild('enterprise', enterpriseSubmission); } + + return save('enterprise', enterpriseSubmission); }); } else { data['fl-credentials'] = 'submission-' + enterpriseSubmission.id; enterpriseSubmission.data = data; notificationSettings = pushData; + savePushData(true); + if (request) { - requestBuild('enterprise', enterpriseSubmission); - } else { - save('enterprise', enterpriseSubmission); + if (!storeFeatures.private) { + Fliplet.Studio.emit('overlay', { + name: 'app-settings', + options: { + size: 'large', + title: 'App Settings', + appId: Fliplet.Env.get('appId'), + section: 'appBilling', + helpLink: 'https://help.fliplet.com/app-settings/' + } + }); + + Fliplet.Studio.emit('track-event', { + category: 'app_billing', + action: 'open', + context: 'apple_launch' + }); + + return; + } + + return requestBuild('enterprise', enterpriseSubmission); } + + return save('enterprise', enterpriseSubmission); } } function saveUnsignedData(request) { - var data = unsignedSubmission.data; + var data = unsignedSubmission.data || {}; $('#unsignedConfiguration [name]').each(function(i, el) { - var name = $(el).attr("name"); + var name = $(el).attr('name'); + + if (name === 'fl-uns-firebase') { + return; // saved in credentials + } + var value = $(el).val(); + if (typeof value === 'string') { + value = value.trim(); + } + data[name] = value; }); + data['fl-credentials'] = 'submission-' + unsignedSubmission.id; + unsignedSubmission.data = data; if (request) { - requestBuild('unsigned', unsignedSubmission); - } else { - save('unsigned', unsignedSubmission); + if (!organizationIsPaying) { + Fliplet.Studio.emit('overlay', { + name: 'app-settings', + options: { + size: 'large', + title: 'App Settings', + appId: Fliplet.Env.get('appId'), + section: 'appBilling', + helpLink: 'https://help.fliplet.com/app-settings/' + } + }); + + Fliplet.Studio.emit('track-event', { + category: 'app_billing', + action: 'open', + context: 'apple_launch' + }); + + return; + } + + return requestBuild('unsigned', unsignedSubmission); } + + return save('unsigned', unsignedSubmission); } -function savePushData() { - var data = notificationSettings; +function savePushData(silentSave) { + var data = notificationSettings || {}; + var pushDataMap = { + 'fl-push-authKey': 'apnAuthKey', + 'fl-push-keyId': 'apnKeyId' + }; $('#pushConfiguration [name]').each(function(i, el) { - var name = $(el).attr("name"); - var value = $(el).val(); + var name = $(el).attr('name'); - if (name === 'fl-push-authKey') { - data.apnAuthKey = value; + if (!pushDataMap.hasOwnProperty(name)) { return; } - if (name === 'fl-push-keyId') { - data.apnKeyId = value; - return; + + var value = $(el).val(); + + if (typeof value === 'string') { + value = value.trim(); } + + data[pushDataMap[name]] = value; }); data.apn = !!((data.apnAuthKey && data.apnAuthKey !== '') && (data.apnKeyId && data.apnKeyId !== '') && (data.apnTeamId && data.apnTeamId !== '') && (data.apnTopic && data.apnTopic !== '')); @@ -860,8 +1517,15 @@ function savePushData() { data: notificationSettings }).then(function() { $('.save-push-progress').addClass('saved'); - if (!notificationSettings.apn) { - alert('Your settings have been saved!\n\nHowever push notifications will only work on App Store and Enterprise apps.\nRequest an app for one of those types and fill in the Bundle ID and Team/Team ID fields.\n\nYou don\'t need to request another app if you have requested an app for App Store or Enterprise before with those two fields filled in already.'); + + if (!notificationSettings.apn && !silentSave) { + Fliplet.Modal.alert({ + title: 'Your settings have been saved!', + message: [ + 'However push notifications will only work on App Store and Enterprise apps. Request an app for one of those types and fill in the Bundle ID and Team/Team ID fields.', + '', + 'You don\'t need to request another app if you have requested an app for App Store or Enterprise before with those two fields filled in already.'].join('
    ') + }); } setTimeout(function() { @@ -870,43 +1534,94 @@ function savePushData() { }); } -function setCredentials(organizationId, id, data) { +function saveProgressOnClose() { + var savingFunctions = { + 'appstore-control': saveAppStoreData, + 'enterprise-control': saveEnterpriseData, + 'unsigned-control': saveUnsignedData + }; + + // Finding out active tab to use correct save method + var activeTabId = $('.nav.nav-tabs li.active').prop('id'); + + return savingFunctions[activeTabId](); +} + +function cloneCredentials(credentialKey, submission, saveData) { return Fliplet.API.request({ - method: 'PUT', - url: 'v1/organizations/' + organizationId + '/credentials/submission-' + id, - data: data - }) - .then(() => { + method: 'POST', + url: 'v1/organizations/' + organizationId + '/credentials/' + credentialKey + '/clone', + data: { + key: submission.data['fl-credentials'] + } + }).then(function() { + if (saveData) { + return Fliplet.App.Submissions.update(submission.id, submission.data); + } + return Promise.resolve(); - }) + }).catch(function(err) { + if (err.status === 400) { + return Fliplet.App.Submissions.update(submission.id, submission.data); + } + }); } -function getTeams(organizationId, id) { - return Fliplet.API.request({ - method: 'GET', - url: 'v1/organizations/' + organizationId + '/credentials/submission-' + id + '/teams?itunes=false' - }) - .then(function(result) { +function setCredentials(id, data, verify) { + verify = typeof verify === 'undefined' ? true : verify; + + return waitForSocketConnection().then(function() { + return Fliplet.API.request({ + method: 'PUT', + url: 'v1/organizations/' + organizationId + '/credentials/submission-' + id + '?verify=' + verify, + data: data + }); + }); +} + +function getTeams(id, isItunes) { + return waitForSocketConnection().then(function() { + return Fliplet.API.request({ + method: 'GET', + url: 'v1/organizations/' + organizationId + '/credentials/submission-' + id + '/teams?itunes=' + isItunes + }); + }).then(function(result) { return Promise.resolve(result.teams); }); } -function searchCredentials(organizationId, data) { +function searchCredentials(data) { return Fliplet.API.request({ method: 'POST', url: 'v1/organizations/' + organizationId + '/credentials/search', data: data - }) - .then(function(credentials) { - return Promise.resolve(credentials); + }).then(function(response) { + if (!response) { + return; + } + + var credentialKey; + var submissionsWithCred = _.filter(Object.keys(response), function(o) { + return response[o].hasCertificate === true; + }); + + credentialKey = _.max(submissionsWithCred, function(o) { + return response[o].updatedAt; + }); + + if (!credentialKey) { + return; + } + + return getCredential(credentialKey); }); } -function getAppCredentials(organizationId, credentialKey, teamId) { - if(credentialKey) { - return getCredential(organizationId, credentialKey) +function getAppCredentials(credentialKey, teamId) { + if (credentialKey) { + return getCredential(credentialKey) .then(function(credential) { - if(credential && credential.teamId === teamId && (credential.p12 || credential.certificate)) { + if (credential && credential.teamId === teamId && (credential.p12 || credential.certificate)) { return credential; } @@ -930,71 +1645,56 @@ function setAppStorePrevCredentials(credential) { Fliplet.Widget.autosize(); } -function refreshAppStoreOptions(devEmail, devPass, selectedTeamId, selectedTeamName) { - if(!selectedTeamId) { +function refreshAppStoreOptions(devEmail, selectedTeamId, selectedTeamName) { + if (!selectedTeamId) { setAppStorePrevCredentials(); + return; } - return getAppCredentials(organizationID, - appStoreSubmission.data['fl-credentials'], + return getAppCredentials(appStoreSubmission.data['fl-credentials'], selectedTeamId) .then(function(credential) { - if(credential) { + if (credential) { return credential; } - return searchCredentials(organizationID, { + return searchCredentials({ email: devEmail, - password: devPass, type: 'apple', teamId: selectedTeamId - }) - .then(function(response) { - var credentialKey; - var submissionsWithCred = _.filter(Object.keys(response), function (o) { - return response[o].hasCertificate === true; - }); - - if (submissionsWithCred) { - credentialKey = _.max(submissionsWithCred, function (o) { - return response[o].updatedAt; - }); - } - - if(credentialKey) { - return getCredential(organizationID, credentialKey); - } - - return; - }) - .catch(function(error) { - return; }); }) .then(function(credential) { - if(credential) { + if (credential) { return credential; } - //if we dont have any credentials we need to check previous result for a credential object - if(appStoreSubmission.data.previousResults && (appStoreSubmission.data.previousResults.p12 || appStoreSubmission.data.previousResults.certificate) && appStoreSubmission.data['fl-store-teamId'] === selectedTeamId) { + var previousResults = appStoreSubmission.data.previousResults; + + // make sure that previous results are obtained from latest completed submission. + if (!_.isUndefined(previousAppStoreSubmission)) { + previousResults = previousAppStoreSubmission.result; + } + + // if we dont have any credentials we need to check previous result for a credential object + if (!_.isUndefined(previousResults) && (!_.isUndefined(previousResults.p12) || !_.isUndefined(previousResults.certificate)) && appStoreSubmission.data['fl-store-teamId'] === selectedTeamId) { return { teamId: selectedTeamId, teamName: selectedTeamName, - certSigningRequest: appStoreSubmission.data.previousResults.certSigningRequest, - p12: appStoreSubmission.data.previousResults.p12, - certificate: appStoreSubmission.data.previousResults.certificate, - content: appStoreSubmission.data.previousResults.content + certSigningRequest: previousResults.certSigningRequest, + p12: previousResults.p12.files[0], + certificate: previousResults.certificate.files[0], + content: previousResults.content }; } - return getCompletedSubmissions(organizationID, devEmail, selectedTeamId, selectedTeamName); + return getCompletedSubmissions(devEmail, selectedTeamId, selectedTeamName); }) .then(function(credential) { return setAppStorePrevCredentials(credential); }) - .catch(function(error) { + .catch(function() { return setAppStorePrevCredentials(); }); } @@ -1012,147 +1712,174 @@ function setAppEnterprisePrevCredential(credential) { Fliplet.Widget.autosize(); } -function refreshAppEnterpriseOptions(devEmail, devPass, selectedTeamId, selectedTeamName) { - if(!selectedTeamId) { +function refreshAppEnterpriseOptions(devEmail, selectedTeamId, selectedTeamName) { + if (!selectedTeamId) { setAppEnterprisePrevCredential(); + return; } - return getAppCredentials(organizationID, - enterpriseSubmission.data['fl-credentials'], + return getAppCredentials(enterpriseSubmission.data['fl-credentials'], selectedTeamId) .then(function(credential) { - if(credential) { + if (credential) { return credential; } - return searchCredentials(organizationID, { + return searchCredentials({ email: devEmail, - password: devPass, type: 'apple-enterprise', teamId: selectedTeamId - }) - .then(function(response) { - var credentialKey; - var submissionsWithCred = _.filter(Object.keys(response), function (o) { - return response[o].hasCertificate === true; - }); - - if (submissionsWithCred) { - credentialKey = _.max(submissionsWithCred, function (o) { - return response[o].updatedAt; - }); - } - - if(credentialKey) { - return getCredential(organizationID, credentialKey); - } - - return; - }) - .catch(function(error) { - return; }); }) .then(function(credential) { - if(credential) { + if (credential) { return credential; } - //if we dont have any credentials we need to check previous result for a credential object - if(enterpriseSubmission.data.previousResults && (latestSubmission.data.previousResults.p12 || enterpriseSubmission.data.previousResults.certificate) && enterpriseSubmission.data['fl-ent-teamId'] === selectedTeamId) { + var previousResults = enterpriseSubmission.data.previousResults; + + // make sure that previous results are obtained from latest completed submission. + if (!_.isUndefined(previousEnterpriseStoreSubmission)) { + previousResults = previousEnterpriseStoreSubmission.result; + } + + // if we dont have any credentials we need to check previous result for a credential object + if (!_.isUndefined(previousResults) && (!_.isUndefined(previousResults.p12) || !_.isUndefined(previousResults.certificate)) && enterpriseSubmission.data['fl-ent-teamId'] === selectedTeamId) { return { teamId: selectedTeamId, teamName: selectedTeamName, - certSigningRequest: enterpriseSubmission.data.previousResults.certSigningRequest, - p12: enterpriseSubmission.data.previousResults.p12, - certificate: enterpriseSubmission.data.previousResults.certificate, - content: enterpriseSubmission.data.previousResults.content + certSigningRequest: previousResults.certSigningRequest, + p12: previousResults.p12.files[0], + certificate: previousResults.certificate.files[0], + content: previousResults.content }; } - return getCompletedSubmissions(organizationID, devEmail, selectedTeamId, selectedTeamName); + return getCompletedSubmissions(devEmail, selectedTeamId, selectedTeamName); }) .then(function(credential) { return setAppEnterprisePrevCredential(credential); }) - .catch(function(error) { + .catch(function() { return setAppEnterprisePrevCredential(); }); } -function getCompletedSubmissions(organizationId, devEmail, teamId, teamName) { +function getCompletedSubmissions(devEmail, teamId, teamName) { + var statusList = [ + 'started', // Default status when the submission is started from the user + 'submitted', // Submission has been submitted + 'queued', // Submission has been sent tot he CI + 'processing', // CI is processing the submission + 'ready-for-testing', // AAB finished and CS should now test the build + 'tested', // Build marked as tested from CS. CI will get informed so can submit to store + 'completed', // Finished or submitted to store + 'failed', // CI failed + 'cancelled' // user canceled + ]; + var statusFilter = _.filter(statusList, function(s) { + return s !== 'failed'; + }); + var url = [ + 'v1/organizations/' + organizationId, + '/submissions?status=' + statusFilter.join(','), + '&email=' + devEmail, + '&teamId=' + teamId + ].join(''); + return Fliplet.API.request({ method: 'GET', - url: 'v1/organizations/' + organizationId + '/submissions?status=completed&email=' + devEmail + '&teamId' + teamId + url: url }) - .then(function(result) { - if(!result.submissions) { - return; - } - var latestSubmission = _.maxBy(result.submissions, function(sub) { - return new Date(sub.updatedAt).getTime(); - }); + .then(function(result) { + if (!result.submissions) { + return; + } - if(latestSubmission && latestSubmission.data.previousResults && (latestSubmission.data.previousResults.p12 || latestSubmission.data.previousResults.certificate)) { - return { - teamId: teamId, - teamName: teamName, - certSigningRequest: latestSubmission.data.previousResults.certSigningRequest, - p12: latestSubmission.data.previousResults.p12, - certificate: latestSubmission.data.previousResults.certificate, - content: latestSubmission.data.previousResults.content - }; - } + var sortedSubmissions = _.orderBy(result.submissions, ['updatedAt'], ['desc']); + var latestSubmission = _.find(sortedSubmissions, function(sub) { + return !_.isUndefined(sub.data.previousResults) + && (!_.isUndefined(sub.data.previousResults.p12) + || !_.isUndefined(sub.data.previousResults.certificate) + ); + }); - return; - }); + if (!_.isUndefined(latestSubmission)) { + return { + teamId: teamId, + teamName: teamName, + certSigningRequest: latestSubmission.data.previousResults.certSigningRequest, + p12: latestSubmission.data.previousResults.p12.files[0], + certificate: latestSubmission.data.previousResults.certificate.files[0], + content: latestSubmission.data.previousResults.content + }; + } + + return; + }); } -function getCredential(organizationId, credentialKey) { +function getCredential(credentialKey) { + if (!credentialKey) { + return Promise.resolve(); + } + return Fliplet.API.request({ method: 'GET', url: 'v1/organizations/' + organizationId + '/credentials/' + credentialKey - }) - .then(function(credential) { - return Promise.resolve(credential); + }).catch(function(error) { + if (error && error.status === 404) { + // Credential not found + return Promise.resolve(); + } + + return Promise.reject(error); }); } -function createCertificates(organizationId, id) { +function createCertificates(options) { + options = options || {}; + return Fliplet.API.request({ method: 'POST', - url: 'v1/organizations/' + organizationId + '/credentials/submission-' + id + '/certificates' + url: 'v1/organizations/' + options.organizationId + '/credentials/submission-' + options.submissionId + '/certificates', + data: { + inHouse: options.inHouse + } }) - .then(function(credential) { - return Promise.resolve(credential); - }); + .then(function(credential) { + return Promise.resolve(credential); + }); } -function setCertificateP12(organizationId, id, file) { +function setCertificateP12(id, file) { return Fliplet.API.request({ method: 'PUT', url: 'v1/organizations/' + organizationId + '/credentials/submission-' + id + '?fileName=p12', data: file, contentType: false, processData: false - }) - .then(function() { - return Promise.resolve(); }); } -function revokeCertificate(organizationId, id, certId) { +function setFirebaseConfigFile(id, file) { + return Fliplet.API.request({ + method: 'PUT', + url: 'v1/organizations/' + organizationId + '/credentials/submission-' + id + '?fileName=firebase', + data: file, + contentType: false, + processData: false + }); +} + +function revokeCertificate(id, certId) { return Fliplet.API.request({ method: 'DELETE', url: 'v1/organizations/' + organizationId + '/credentials/submission-' + id + '/' + certId - }) - .then(function(result) { - return Promise.resolve(); }); } - function init() { Fliplet.Apps.get().then(function(apps) { appInfo = _.find(apps, function(app) { @@ -1160,7 +1887,9 @@ function init() { }); }); - $('#fl-store-keywords').tokenfield(); + $('#fl-store-keywords').tokenfield({ + createTokensOnBlur: true + }); /* APP ICON */ if (appIcon) { @@ -1198,373 +1927,1747 @@ function checkGroupErrors() { }); } -/* ATTACH LISTENERS */ -$('[name="fl-store-screenshots"]').on('change', function() { - var value = $(this).val(); - var id = $(this).attr('id'); - checkHasScreenshots(); +// We set required attribute to 'demo password' only if 'demo user' field is not empty +function checkDemoUser() { + // When google tries to auto-fill 'demo user' field, we checking data from API and delete google auto-fill + // if no saved data for this field + // To allow a user to use auto-fill from google but disallow google to put the information to the field by itself. + // We check how many times use google auto-fill after numerous tries found out that google inserts data to this input + // only three times at a row, so, therefore, the fourth time it's a user trying to input information from auto-fill. + var $demoUserFiled = $('#fl-store-revDemoUser'); + + if (!userInput && autoFill < 3) { + $demoUserFiled.val(demoUser ? demoUser : ''); + autoFill++; + } - if (value === 'new' && !haveScreenshots) { - $('[data-item="fl-store-screenshots-new-warning"]').addClass('show'); + $('#fl-store-revDemoPass').prop('required', $demoUserFiled.val() !== ''); +} - $('[data-item="fl-store-screenshots-new"]').removeClass('show'); - $('[data-item="fl-store-screenshots-existing"]').removeClass('show'); - } - if (value === 'new' && haveScreenshots) { - $('[data-item="fl-store-screenshots-new-warning"]').removeClass('show'); - $('[data-item="fl-store-screenshots-new"]').addClass('show'); +function isValidVersion(version) { + return /^[0-9]{1,2}\.[0-9]{1,2}\.[0-9]{1,2}$/.test(version); +} + +function validateScreenshots() { + var imageErrors = []; + var supportedFormats = _.uniqBy(_.concat.apply(null, _.map(screenshotRequirements, 'sizes')), + function(req) { + return req[0] + ' x ' + req[1]; + }); - $('[data-item="fl-store-screenshots-existing"]').removeClass('show'); + _.forEach(screenshotRequirements, function(req) { + _.forEach(req.screenshots, function(screenshot) { + var supportedSize = _.some(req.sizes, function(size) { + return size[0] === screenshot.size[0] && size[1] === screenshot.size[1]; + }); + // @BUG This should be using the && logic operator. Otherwise, it's ignoring the size check. + // However, there's an API bug where sizes are not stored using the correct pixel sizes, + // so we're allowing this bug for now. + // https://github.com/Fliplet/fliplet-studio/issues/4109 + if (screenshot.appId || supportedSize) { + return; + } - _.take(screenShotsMobile, 4).forEach(function(thumb) { - $('.mobile-thumbs').append(addThumb(thumb)); + imageErrors.push(screenshot.name + ' - ' + screenshot.size[0] + ' x ' + screenshot.size[1]); }); + }); - _.take(screenShotsTablet, 4).forEach(function(thumb) { - $('.tablet-thumbs').append(addThumb(thumb)); + if (imageErrors.length > 0) { + imageErrors.push('Supported screenshot sizes are:'); + imageErrors.push(_.map(supportedFormats, function(format) { + return format[0] + ' × ' + format[1]; + }).join(' | ')); + Fliplet.Modal.alert({ + title: 'The following screenshots have an invalid size', + message: _.join(imageErrors, '
    ') }); - } - if (value === 'existing') { - $('.app-details-appStore .app-screenshots').removeClass('has-error'); - $('[data-item="fl-store-screenshots-existing"]').addClass('show'); - $('[data-item="fl-store-screenshots-new-warning"]').removeClass('show'); - $('[data-item="fl-store-screenshots-new"]').removeClass('show'); + return false; } -}); -$('[name="submissionType"]').on('change', function() { - var selectedOptionId = $(this).attr('id'); - - $('.fl-sb-panel').removeClass('show'); - $('.' + selectedOptionId).addClass('show'); + return true; +} - Fliplet.Widget.autosize(); -}); +function hideError($element, $error) { + $element.removeClass('has-error'); + $error.addClass('hidden'); +} -$('[name="fl-store-credentials"]').on('change', function() { - var value = $(this).val(); +function showError($element, $error) { + $element.addClass('has-error'); + $error.removeClass('hidden'); +} - if (value === 'useOwn') { - $('.fl-store-credential.indented-area').removeClass('hidden'); - } else { - $('.fl-store-credential.indented-area').addClass('hidden'); - } +function validateImageUrl(url, $image, $error) { + return new Promise(function(resolve, reject) { + var img = document.createElement('img'); - Fliplet.Widget.autosize(); -}); + img.onload = resolve; + img.onerror = reject; + img.src = url; + }).then(function() { + hideError($image, $error); -$('.fl-sb-appStore [change-bundleid], .fl-sb-enterprise [change-bundleid], .fl-sb-unsigned [change-bundleid]').on('click', function() { - var changeBundleId = confirm("Are you sure you want to change the unique Bundle ID?"); + return; + }).catch(function() { + showError($image, $error); + }); +} - if (changeBundleId) { - $('.fl-bundleId-holder').addClass('hidden'); - $('.fl-bundleId-field').addClass('show'); - - Fliplet.Widget.autosize(); - } -}); +function publishApp(context) { + var options = { + release: { + type: 'silent', + changelog: 'Initial version' + } + }; -$('.panel-group').on('shown.bs.collapse', '.panel-collapse', function() { - Fliplet.Widget.autosize(); - }) - .on('hidden.bs.collapse', '.panel-collapse', function() { - Fliplet.Widget.autosize(); - }); + return Fliplet.API.request({ + method: 'POST', + url: 'v1/apps/' + Fliplet.Env.get('appId') + '/publish', + data: options + }).then(function(response) { + // Update appInfo + appInfo.productionAppId = response.app.id; + + switch (context) { + case 'appStore': + $('.button-appStore-request').html('Request App '); + $('.button-appStore-request').prop('disabled', false); + $('#appStoreConfiguration').validator().trigger('submit'); + break; + case 'enterprise': + $('.button-enterprise-request').html('Request App '); + $('.button-enterprise-request').prop('disabled', false); + $('#enterpriseConfiguration').validator().trigger('submit'); + break; + case 'unsigned': + $('.button-unsigned-request').html('Request App '); + $('.button-unsigned-request').prop('disabled', false); + $('#unsignedConfiguration').validator().trigger('submit'); + break; + default: + break; + } + }).catch(function(err) { + Fliplet.Modal.alert({ message: Fliplet.parseError(err) }); -$('a[data-toggle="tab"').on('shown.bs.tab', function() { - Fliplet.Widget.autosize(); - }) - .on('hidden.bs.tab', function() { - Fliplet.Widget.autosize(); + return Promise.reject(err); }); +} -$('[name="fl-store-keywords"]').on('tokenfield:createtoken', function(e) { - var currentValue = e.currentTarget.value.replace(/,\s+/g, ','); - var newValue = e.attrs.value; - var oldAndNew = currentValue + ',' + newValue; +function compileStatusTable(withData, origin, buildsData) { + if (withData) { + var template = Handlebars.compile(statusTableTemplate); + var html = template(buildsData); - if (oldAndNew.length > 100) { - e.preventDefault(); - } -}); + if (origin === 'appStore') { + $statusAppStoreTableElement.html(html); + } -$('.redirectToSettings, [data-change-settings]').on('click', function(event) { - event.preventDefault(); + if (origin === 'enterprise') { + $statusEnterpriseTableElement.html(html); + } - Fliplet.Studio.emit('navigate', { - name: 'appSettings', - params: { - appId: Fliplet.Env.get('appId') + if (origin === 'unsigned') { + $statusUnsignedTableElement.html(html); + } + } else { + if (origin === 'appStore') { + $statusAppStoreTableElement.html(''); } - }); -}); -$('[data-change-assets]').on('click', function(event) { - event.preventDefault(); + if (origin === 'enterprise') { + $statusEnterpriseTableElement.html(''); + } - Fliplet.Studio.emit('navigate', { - name: 'launchAssets', - params: { - appId: Fliplet.Env.get('appId') + if (origin === 'unsigned') { + $statusUnsignedTableElement.html(''); } - }); -}); + } -$('#appStoreConfiguration, #enterpriseConfiguration, #unsignedConfiguration').on('validated.bs.validator', function() { - checkGroupErrors(); Fliplet.Widget.autosize(); -}); - -$('#appStoreConfiguration').validator().on('submit', function(event) { - if (event.isDefaultPrevented()) { - // Gives time to Validator to apply classes - setTimeout(checkGroupErrors, 0); - alert('Please fill in all the required information.'); - return; - } +} - event.preventDefault(); +function checkSubmissionStatus(origin, iosSubmissions) { + var submissionsToShow = _.filter(iosSubmissions, function(submission) { + return submission.status === 'queued' || submission.status === 'submitted' || submission.status === 'processing' || submission.status === 'completed' || submission.status === 'failed' || submission.status === 'cancelled' || submission.status === 'ready-for-testing' || submission.status === 'tested'; + }); - if (appInfo && appInfo.productionAppId) { - if (allAppData.indexOf('appStore') > -1) { - if (appStoreLoggedIn) { - var requestAppConfirm; + var buildsData = []; + var submissionTypePrefixes = { + appStore: 'fl-store-', + enterprise: 'fl-ent-', + unsigned: 'fl-uns-' + }; - if (appStoreSubmission.status === "started") { - requestAppConfirm = confirm("Are you sure you wish to request your app to be published?"); + if (submissionsToShow.length) { + submissionsToShow.forEach(function(submission) { + var build = {}; + var appBuild; + var debugHtmlPage; + var submissionType = _.get(submission, 'data.submissionType'); + + // Default copy for testing status for different users + if (submission.status === 'ready-for-testing') { + if (userInfo && userInfo.user && (userInfo.user.isAdmin || userInfo.user.isImpersonating)) { + // Fliplet users + build.testingStatus = 'Ready for testing'; + build.testingMessage = 'App is ready for testing'; } else { - requestAppConfirm = confirm("Are you sure you wish to update your published app?"); + // Normal users + build.testingStatus = 'In testing'; + build.testingMessage = 'Your app is being tested by Fliplet'; } + } - if (requestAppConfirm) { - saveAppStoreData(true); - } - } else { - alert('You need to login with your Apple developer account details.\nSelect one option to provide use with a distribution certificate.'); + if (submission.result.appBuild && submission.result.appBuild.files) { + appBuild = _.find(submission.result.appBuild.files, function(file) { + return file.contentType === 'application/octet-stream'; + }); + } else if (submission.data.previousResults && submission.data.previousResults.appBuild && submission.data.previousResults.appBuild.files) { + appBuild = _.find(submission.data.previousResults.appBuild.files, function(file) { + return file.contentType === 'application/octet-stream'; + }); } - } else { - alert('Please configure your App Settings to contain the required information.'); - } - } else { - alert('You need to publish this app first.\nGo to "Step 1. Prepare your app" to publish your app.'); - } - // Gives time to Validator to apply classes - setTimeout(checkGroupErrors, 0); -}); + if (submission.result.debugHtmlPage && submission.result.debugHtmlPage.files) { + debugHtmlPage = _.find(submission.result.debugHtmlPage.files, function(file) { + return file.contentType === 'text/html'; + }); + } else if (submission.data.previousResults && submission.data.previousResults.debugHtmlPage && submission.data.previousResults.debugHtmlPage.files) { + debugHtmlPage = _.find(submission.data.previousResults.debugHtmlPage.files, function(file) { + return file.contentType === 'text/html'; + }); + } -$('#enterpriseConfiguration').validator().on('submit', function(event) { - if (event.isDefaultPrevented()) { - // Gives time to Validator to apply classes - setTimeout(checkGroupErrors, 0); - alert('Please fill in all the required information.'); - return; - } + build.id = submission.id; + build.updatedAt = ((submission.status === 'completed' || submission.status === 'failed' || submission.status === 'cancelled' || submission.status === 'ready-for-testing' || submission.status === 'tested') && submission.updatedAt) + ? moment(submission.updatedAt).format('MMM Do YYYY, h:mm:ss a') + : ''; + build.submittedAt = ((submission.status === 'queued' || submission.status === 'submitted') && submission.submittedAt) + ? moment(submission.submittedAt).format('MMM Do YYYY, h:mm:ss a') + : ''; - event.preventDefault(); + build.fileUrl = appBuild ? removeAuthTokenFromFileUrl(appBuild.url) : ''; + build.versionNumber = _.get(submission, ['data', submissionTypePrefixes[submissionType] + 'versionNumber']); - if (!enterpriseManual && !enterpriseLoggedIn) { - alert('Please log in with the Apple Developer Account or choose to enter the data manually.'); - return; - } + if (submission.result.message) { + build.message = submission.result.message; - if (appInfo && appInfo.productionAppId) { - if (allAppData.indexOf('enterprise') > -1) { - var requestAppConfirm; + const gracefullyFailedMessage = 'The app has been uploaded to the App Store Connect but could not be submitted for review. Please log in on https://appstoreconnect.apple.com/ to finish up the process and submit the app for review.'; - if (enterpriseSubmission.status === "started") { - requestAppConfirm = confirm("Are you sure you wish to request your app to be published?"); - } else { - requestAppConfirm = confirm("Are you sure you wish to update your published app?"); + if (submission.result.message === gracefullyFailedMessage) { + submission.status = 'gracefullyFailed'; + } } - if (requestAppConfirm) { - saveEnterpriseData(true); + build[submission.status] = true; + + if (submission.result.errorCode < 0) { + build.message = 'There was an error processing your submission. To learn more, go to help.fliplet.com/common-apple-issues.'; } - } else { - alert('Please configure your App Settings to contain the required information.'); - } + + if (userInfo && userInfo.user && (userInfo.user.isAdmin || userInfo.user.isImpersonating)) { + build.isAdmin = userInfo.user.isAdmin || userInfo.user.isImpersonating; + build.debugFileUrl = debugHtmlPage ? removeAuthTokenFromFileUrl(debugHtmlPage.url) : ''; + } + + buildsData.push(build); + }); + + compileStatusTable(true, origin, buildsData); } else { - alert('You need to publish this app first.\nGo to "Step 1. Prepare your app" to publish your app.'); + compileStatusTable(false, origin); } +} - // Gives time to Validator to apply classes - setTimeout(checkGroupErrors, 0); -}); +function submissionChecker(submissions) { + // --------------------- + // App Store submissions + // --------------------- -$('#unsignedConfiguration').validator().on('submit', function(event) { - if (event.isDefaultPrevented()) { - // Gives time to Validator to apply classes - setTimeout(checkGroupErrors, 0); - alert('Please fill in all the required information.'); - return; - } + var asub = _.filter(submissions, function(submission) { + return submission.data.submissionType === 'appStore' && submission.platform === 'ios'; + }); + var prevSubCred; + var previousSubWithCredentials; - event.preventDefault(); + var completedAsub = _.filter(asub, function(submission) { + return submission.status === 'completed'; + }); - if (appInfo && appInfo.productionAppId) { - if (allAppData.indexOf('unsigned') > -1) { - var requestAppConfirm; + // Get the Submission data from the latest completed submission, + // it has the certification values that are in use on the app store. + previousAppStoreSubmission = _.maxBy(completedAsub, function(el) { + return el.id; + }); - if (unsignedSubmission.status === "started") { - requestAppConfirm = confirm("Are you sure you wish to request your app to be published?"); - } else { - requestAppConfirm = confirm("Are you sure you wish to update your published app?"); - } + appStoreSubmissionInStore = (completedAsub.length > 0); - if (requestAppConfirm) { - saveUnsignedData(true); - } - } else { - alert('Please configure your App Settings to contain the required information.'); - } - } else { - alert('You need to publish this app first.\nGo to "Step 1. Prepare your app" to publish your app.'); + asub = _.orderBy(asub, function(submission) { + return new Date(submission.createdAt).getTime(); + }, ['desc']); + checkSubmissionStatus('appStore', asub); + + appStoreSubmission = _.maxBy(asub, function(el) { + return new Date(el.createdAt).getTime(); + }); + + if (!appStoreSubmission) { + appStoreSubmission = {}; } - // Gives time to Validator to apply classes - setTimeout(checkGroupErrors, 0); -}); + var cloneAppStoreCredentialsPromise = Promise.resolve(); -/* SAVE PROGRESS CLICK */ -$('[data-app-store-save]').on('click', function() { - saveAppStoreData(); -}); -$('[data-enterprise-save]').on('click', function() { - saveEnterpriseData(); -}); -$('[data-unsigned-save]').on('click', function() { - saveUnsignedData(); -}); -$('[data-push-save]').on('click', function() { - savePushData(); -}); + if (appStoreSubmission.data && !appStoreSubmission.data['fl-credentials']) { + prevSubCred = _.filter(asub, function(submission) { + return submission.data && submission.data['fl-credentials']; + }); -/* Credentials and Certificates App Store */ -$('.login-appStore-button').on('click', function() { - var $this = $(this); - $(this).html('Logging in...'); - $(this).addClass('disabled'); - var devEmail = $('#fl-store-appDevLogin').val(); - var devPass = $('#fl-store-appDevPass').val(); - var emailError = $('#fl-store-appDevLogin').data('error'); - var passError = $('#fl-store-appDevPass').data('error'); + previousSubWithCredentials = _.maxBy(prevSubCred, function(el) { + return new Date(el.createdAt).getTime(); + }); - // Remove errors - $('#fl-store-appDevLogin').parents('.form-group').removeClass('has-error has-danger'); - $('#fl-store-appDevLogin').next('.with-errors').html(''); - $('#fl-store-appDevPass').parents('.form-group').removeClass('has-error has-danger'); - $('#fl-store-appDevPass').next('.with-errors').html(''); - $this.nextAll('login-error').html(''); + appStoreSubmission.data['fl-credentials'] = 'submission-' + appStoreSubmission.id; - if (devEmail === '') { - $('#fl-store-appDevLogin').parents('.form-group').addClass('has-error has-danger'); - $('#fl-store-appDevLogin').next('.with-errors').html(emailError); - $(this).html('Log in'); - $(this).removeClass('disabled'); - Fliplet.Widget.autosize(); + if (previousSubWithCredentials) { + cloneAppStoreCredentialsPromise = cloneCredentials(previousSubWithCredentials.data['fl-credentials'], appStoreSubmission, true); + } } - if (devPass === '') { - $('#fl-store-appDevPass').parents('.form-group').addClass('has-error has-danger'); - $('#fl-store-appDevPass').next('.with-errors').html(passError); - $(this).html('Log in'); - $(this).removeClass('disabled'); - Fliplet.Widget.autosize(); - } + // ---------------------- + // Enterprise submissions + // ---------------------- - if (devEmail !== '' && devPass !== '') { - setCredentials(organizationID, appStoreSubmission.id, { - type: 'apple', - status: 'created', - email: devEmail, - password: devPass - }) - .then(function() { - return getTeams(organizationID, appStoreSubmission.id) - .then(function(teams) { - var appStoreTeams = _.filter(teams, function(team) { - return team.type === "Company/Organization"; - }) - appStoreTeams.forEach(function(team, i) { - $('.appStore-team').append(''); - }); + var esub = _.filter(submissions, function(submission) { + return submission.data.submissionType === 'enterprise' && submission.platform === 'ios'; + }); - $this.html('Log in'); - $this.removeClass('disabled'); - $('.appStore-logged-emai').html(devEmail); - $('.appStore-login-details').addClass('hidden'); - $('.appStore-logged-in, .appStore-teams').addClass('show'); - appStoreLoggedIn = true; - var teamId = $('#fl-store-teams').val(); - var teamName = teamId ? $('#fl-store-teams').find(":selected").data('team-name') : ''; - - if(teamId) { - $('.appStore-more-options').addClass('show'); - } else { - $('.appStore-more-options').removeClass('show'); - } + var completedEsub = _.filter(esub, function(submission) { + return submission.status === 'completed'; + }); - return refreshAppStoreOptions(devEmail, devPass, teamId, teamName); - }); + // Get the Submission data from the latest completed submission, + // it has certification values that are in use on the developer portal. + previousEnterpriseStoreSubmission = _.maxBy(completedEsub, function(el) { + return el.id; + }); - }) - .catch(function(error) { - console.log(error); - if (error.responseJSON) { - $this.nextAll('.login-error').html(error.responseJSON.message); - } - $this.html('Log in'); - $this.removeClass('disabled'); - Fliplet.Widget.autosize(); - }); + esub = _.orderBy(esub, function(submission) { + return new Date(submission.createdAt).getTime(); + }, ['desc']); + checkSubmissionStatus('enterprise', esub); + + enterpriseSubmission = _.maxBy(esub, function(el) { + return new Date(el.createdAt).getTime(); + }); + + if (!enterpriseSubmission) { + enterpriseSubmission = {}; } -}); -$('.log-out-appStore').on('click', function() { - appStoreLoggedIn = false; - $('.appStore-logged-emai').html(''); - $('.appStore-login-details').removeClass('hidden'); - $('.appStore-logged-in, .appStore-more-options, .appStore-teams').removeClass('show'); -}); + var cloneEnterpriseCredentialsPromise = Promise.resolve(); -$('[name="fl-store-distribution"]').on('change', function() { - var value = $(this).val(); + if (enterpriseSubmission.data && !enterpriseSubmission.data['fl-credentials']) { + prevSubCred = _.filter(esub, function(submission) { + return submission.data && submission.data['fl-credentials']; + }); - $('#fl-store-teams').prop('required',true); + previousSubWithCredentials = _.maxBy(prevSubCred, function(el) { + return new Date(el.createdAt).getTime(); + }); - if (value === 'previous-file') { - if (appStoreCertificateReplaced) { - $('.appStore-previous-file-success').addClass('show'); - } + enterpriseSubmission.data['fl-credentials'] = 'submission-' + enterpriseSubmission.id; - $('.appStore-generate-file, .appStore-generate-file-success, .appStore-upload-file').removeClass('show'); - $('#fl-store-certificate').prop('required',false); - } - if (value === 'generate-file') { - if (appStoreCertificateCreated) { - $('.appStore-generate-file-success').addClass('show'); - } else { - $('.appStore-generate-file').addClass('show'); + if (previousSubWithCredentials) { + cloneEnterpriseCredentialsPromise = cloneCredentials(previousSubWithCredentials.data['fl-credentials'], enterpriseSubmission, true); } - - $('.appStore-previous-file-success, .appStore-upload-file').removeClass('show'); - $('#fl-store-certificate').prop('required',false); } - if (value === 'upload-file') { - $('.appStore-upload-file').addClass('show'); - $('.appStore-previous-file-success, .appStore-generate-file, .appStore-generate-file-success').removeClass('show'); - $('#fl-store-certificate').prop('required',true); - } + // -------------------- + // Unsigned submissions + // -------------------- + + var usub = _.filter(submissions, function(submission) { + return submission.data.submissionType === 'unsigned' && submission.platform === 'ios'; + }); + + usub = _.orderBy(usub, function(submission) { + return new Date(submission.createdAt).getTime(); + }, ['desc']); + + checkSubmissionStatus('unsigned', usub); + + unsignedSubmission = _.maxBy(usub, function(el) { + return new Date(el.createdAt).getTime(); + }); + + if (!unsignedSubmission) { + unsignedSubmission = {}; + } + + var cloneUnsignedCredentialsPromise = Promise.resolve(); + + if (unsignedSubmission.data && !unsignedSubmission.data['fl-credentials']) { + prevSubCred = _.filter(usub, function(submission) { + return submission.data && submission.data['fl-credentials']; + }); + + previousSubWithCredentials = _.maxBy(prevSubCred, function(el) { + return new Date(el.createdAt).getTime(); + }); + + unsignedSubmission.data['fl-credentials'] = 'submission-' + unsignedSubmission.id; + + if (previousSubWithCredentials) { + cloneUnsignedCredentialsPromise = cloneCredentials(previousSubWithCredentials.data['fl-credentials'], unsignedSubmission, true); + } + } + + return cloneAppStoreCredentialsPromise.then(function() { + return cloneEnterpriseCredentialsPromise; + }).then(function() { + return cloneUnsignedCredentialsPromise; + }).then(function() { + if (_.isEmpty(appStoreSubmission)) { + return Fliplet.App.Submissions.create({ + platform: 'ios', + data: { + submissionType: 'appStore' + } + }) + .then(function(submission) { + appStoreSubmission = submission; + + return Promise.resolve(); + }); + } + + return Promise.resolve(); + }).then(function() { + if (_.isEmpty(enterpriseSubmission)) { + return Fliplet.App.Submissions.create({ + platform: 'ios', + data: { + submissionType: 'enterprise' + } + }) + .then(function(submission) { + enterpriseSubmission = submission; + + return Promise.resolve(); + }); + } + + return Promise.resolve(); + }).then(function() { + if (_.isEmpty(unsignedSubmission)) { + return Fliplet.App.Submissions.create({ + platform: 'ios', + data: { + submissionType: 'unsigned' + } + }) + .then(function(submission) { + unsignedSubmission = submission; + + return Promise.resolve(); + }); + } + + return Promise.resolve(); + }); +} + +function iosSubmissionChecker(submissions) { + var asub = _.filter(submissions, function(submission) { + return submission.data.submissionType === 'appStore' && submission.platform === 'ios'; + }); + + var esub = _.filter(submissions, function(submission) { + return submission.data.submissionType === 'enterprise' && submission.platform === 'ios'; + }); + + var usub = _.filter(submissions, function(submission) { + return submission.data.submissionType === 'unsigned' && submission.platform === 'ios'; + }); + + // Ordering + asub = _.orderBy(asub, function(submission) { + return new Date(submission.createdAt).getTime(); + }, ['desc']); + esub = _.orderBy(esub, function(submission) { + return new Date(submission.createdAt).getTime(); + }, ['desc']); + usub = _.orderBy(usub, function(submission) { + return new Date(submission.createdAt).getTime(); + }, ['desc']); + + checkSubmissionStatus('appStore', asub); + checkSubmissionStatus('enterprise', esub); + checkSubmissionStatus('unsigned', usub); +} + +function getSubmissions() { + return Fliplet.App.Submissions.get(); +} + +function setFirebaseStatus(credentialKey, origin) { + if (!credentialKey) { + return; + } + + var environment; + + switch (origin) { + case 'appStore': + environment = 'store'; + break; + case 'enterprise': + environment = 'ent'; + break; + case 'unsigned': + environment = 'uns'; + break; + default: + environment = false; + break; + } + + if (!environment) { + console.error('Invalid environment'); + + return; + } + + getCredential(credentialKey).then(function(credentials) { + if (_.get(credentials, 'firebase.url')) { + $('#fl-' + environment + '-firebase-status').html('Enabled').addClass('analytics-success'); + } + }).catch(function(error) { + console.error(error); + }); +} + +function initialLoad(initial, timeout) { + if (!initial) { + initLoad = setTimeout(function() { + getSubmissions() + .then(function(submissions) { + iosSubmissionChecker(submissions); + initialLoad(false, 15000); + }); + }, timeout); + } else { + getSubmissions() + .then(function(submissions) { + if (submissions.length) { + submissions.forEach(function(submission) { + if (submission.data['fl-credentials'] && submission.platform === 'ios') { + setFirebaseStatus(submission.data['fl-credentials'], submission.data.submissionType); + } + }); + } else { + return Promise.all([ + Fliplet.App.Submissions.create({ + platform: 'ios', + data: { + submissionType: 'appStore' + } + }) + .then(function(submission) { + appStoreSubmission = submission; + }), + Fliplet.App.Submissions.create({ + platform: 'ios', + data: { + submissionType: 'unsigned' + } + }) + .then(function(submission) { + unsignedSubmission = submission; + }), + Fliplet.App.Submissions.create({ + platform: 'ios', + data: { + submissionType: 'enterprise' + } + }) + .then(function(submission) { + enterpriseSubmission = submission; + }) + ]); + } + + return Fliplet.API.request({ + cache: true, + url: 'v1/user' + }) + .then(function(user) { + userInfo = user; + + return submissionChecker(submissions); + }); + }) + .then(function() { + // Fliplet.Env.get('appId') + // Fliplet.Env.get('appName') + // Fliplet.Env.get('appSettings') + + return Promise.all([ + Fliplet.API.request({ + method: 'GET', + url: 'v1/apps/' + Fliplet.Env.get('appId') + }) + .then(function(result) { + appName = result.app.name; + appIcon = result.app.icon; + appSettings = result.app.settings; + }), + Fliplet.API.request({ + method: 'GET', + url: 'v1/organizations/' + organizationId + }) + .then(function(org) { + organizationName = org.name; + }), + Fliplet.API.request({ + cache: true, + url: 'v1/widgets?include_instances=true&tags=type:appComponent&appId=' + Fliplet.Env.get('appId') + '&package=com.fliplet.analytics' + }) + .then(function(res) { + var isEnabled = !_.isEmpty(res.widgets[0].instances); + + if (isEnabled) { + $('[data-fl-analytics-status]').html('Enabled').addClass('analytics-success'); + } + }) + ]); + }) + .then(function() { + if (appSettings.folderStructure) { + var structure = []; + + hasFolders = true; + + var appleOnly = _.filter(appSettings.folderStructure, function(obj) { + return obj.platform === 'apple'; + }); + + return Promise.all(appleOnly.map(function(obj) { + return Fliplet.Media.Folders.get({ folderId: obj.folderId }) + .then(function(result) { + var tempObject = { + type: obj.type, + folderContent: result + }; + + structure.push(tempObject); + + return Promise.resolve(structure); + }); + })) + .then(function() { + structure.forEach(function(el) { + var idx = _.findIndex(screenshotRequirements, { + type: el.type + }); + + if (idx > -1) { + screenshotRequirements[idx].screenshots = el.folderContent.files; + } + }); + }); + } + + hasFolders = false; + + return; + }) + .then(function() { + return Fliplet.API.request({ + method: 'GET', + url: 'v1/widget-instances/com.fliplet.push-notifications?appId=' + Fliplet.Env.get('appId') + }); + }) + .then(function(response) { + if (response.widgetInstance.settings && response.widgetInstance.settings) { + notificationSettings = response.widgetInstance.settings; + } else { + notificationSettings = {}; + } + + init(); + initialLoad(false, 5000); + }); + } +} + +function updateServerLocation() { + var region = Fliplet.User.getAuthToken().substr(0, 2); + var serverLocations = { + eu: 'Dublin, Ireland', + us: 'San Francisco, US' + }; + + if (serverLocations[region]) { + $('.region-server-location').html(serverLocations[region]); + } +} + +function getCurrentLoginForm() { + var id = $('.nav-tabs > li.active').prop('id'); + + if (!id) { + return; + } + + var ids = { + 'appstore-control': 'app-store', + 'enterprise-control': 'enterprise' + }; + + return ids[id]; +} + +function mapSelectors(selectors, keys) { + selectors = selectors || {}; + + if (typeof keys === 'string') { + keys = [keys]; + } + + keys = keys || []; + + return _.values(_.pick(selectors, keys)).join(','); +} + +function toggleLoginForm(form, state, data) { + // form @param (String) app-store | enterprise + // state @param (String) login | logging-in | 2fa-device | 2fa-waiting | 2fa-code | 2fa-verifying | logged-in + // data @param (Object) Data for configuring forms (Optional) + + var selectors = { + 'app-store': { + emailField: '#fl-store-appDevLogin', + passwordField: '#fl-store-appDevPass', + loginButton: '.login-appStore-button', + mfaDevices: '.appStore-login-2fa-devices', + mfaDeviceField: '#fl-store-2fa-select', + mfaDeviceName: 'fl-store-device', + mfaCodeWaiting: '.appStore-login-2fa-waiting', + mfaCode: '.appStore-login-2fa-code', + mfaSms: '.appStore-2fa-sms', + mfaCodeField: '#fl-store-2fa-code', + mfaCodeButton: '.2fa-code-store-button', + loginDetails: '.appStore-login-details', + loggedInEmail: '.appStore-logged-email', + loggedIn: '.appStore-logged-in', + moreOptions: '.appStore-more-options', + teams: '.appStore-teams' + }, + enterprise: { + emailField: '#fl-ent-appDevLogin', + passwordField: '#fl-ent-appDevPass', + loginButton: '.login-enterprise-button', + mfaDevices: '.enterprise-login-2fa-devices', + mfaDeviceField: '#fl-ent-2fa-select', + mfaDeviceName: 'fl-ent-device', + mfaCodeWaiting: '.enterprise-login-2fa-waiting', + mfaCode: '.enterprise-login-2fa-code', + mfaSms: '.enterprise-2fa-sms', + mfaCodeField: '#fl-ent-2fa-code', + mfaCodeButton: '.2fa-code-ent-button', + loginDetails: '.enterprise-login-details', + loggedInEmail: '.enterprise-logged-email', + loggedIn: '.enterprise-logged-in', + moreOptions: '.enterprise-more-options', + teams: '.enterprise-teams' + } + }; + + if (!Object.keys(selectors).indexOf(form) === -1) { + // Invalid form + return; + } + + var sel = selectors[form]; + + data = data || {}; + + switch (state) { + case 'login': + $(mapSelectors(sel, ['emailField', 'passwordField'])).prop({ + readonly: false, + required: true + }); + $(mapSelectors(sel, ['mfaDeviceField', 'mfaCodeField'])).prop('required', false); + $(sel.loginButton).html('Log in').removeClass('disabled'); + $(sel.loginDetails).removeClass('hidden'); + $(mapSelectors(sel, ['mfaDevices', 'mfaCode', 'loggedIn', 'moreOptions', 'teams'])).removeClass('show'); + break; + case 'logging-in': + $(mapSelectors(['emailField', 'passwordField'])).prop({ + readonly: true + }); + $(sel.loginButton).html('Logging in ' + spinner).addClass('disabled'); + $(sel.loginDetails).removeClass('hidden'); + $(mapSelectors(sel, ['mfaDevices', 'mfaCode', 'loggedIn', 'moreOptions', 'teams'])).removeClass('show'); + break; + case '2fa-device': + var options = _.map(_.get(data, 'devices', []), function eachDevice(device, i) { + return [ + '', + '' + device, + '' + ].join(''); + }).join(''); + + $(mapSelectors(sel, ['emailField', 'passwordField', 'mfaCodeField'])).prop({ + required: false + }); + $(sel.mfaDeviceField).html(options).find('input').prop('required', true); + $(mapSelectors(sel, ['emailField', 'passwordField', 'mfaCodeField'])).prop('required', false); + $(sel.mfaDevices).addClass('show'); + $(sel.loginDetails).addClass('hidden'); + $(mapSelectors(sel, ['mfaCodeWaiting', 'mfaCode', 'loggedIn', 'moreOptions', 'teams'])).removeClass('show'); + break; + case '2fa-waiting': + $(sel.mfaCodeWaiting).addClass('show'); + $(mapSelectors(sel, ['mfaDeviceField', 'mfaCodeField'])).prop('required', false); + $(mapSelectors(sel, ['mfaCode', 'mfaDevices', 'mfaSms'])).removeClass('show'); + break; + case '2fa-code': + $(sel.mfaCode).addClass('show'); + $(sel.mfaCodeField).val('').prop({ + required: true, + readonly: false + }).focus(); + // Show SMS option if allowed + $(sel.mfaSms)[data.smsAllowed ? 'addClass' : 'removeClass']('show'); + $(sel.mfaCodeButton).html('Verify').prop('disabled', false); + $(mapSelectors(sel, 'emailField, passwordField, mfaDeviceField')).prop('required', false); + $(sel.loginDetails).addClass('hidden'); + $(mapSelectors(sel, ['mfaDevices', 'mfaCodeWaiting', 'loggedIn', 'moreOptions', 'teams'])).removeClass('show'); + break; + case '2fa-verifying': + $(sel.mfaCodeField).prop('readonly', true); + $(sel.mfaCodeButton).html('Verifying ' + spinner).prop('disabled', true); + break; + case 'logged-in': + $(mapSelectors(sel, ['emailField', 'passwordField', 'mfaDeviceField', 'mfaCodeField'])).prop({ + required: false + }); + $(sel.emailField).val(data.email); + $(sel.loggedInEmail).html(data.email); + $(sel.loginDetails).addClass('hidden'); + $(mapSelectors(sel, ['loggedIn', 'teams'])).addClass('show'); + $(mapSelectors(sel, ['mfaDevices', 'mfaCode'])).removeClass('show'); + break; + default: + break; + } + + Fliplet.Widget.autosize(); +} + +function removeAuthTokenFromFileUrl(url) { + var tokenFound = url.match(/auth_token=([A-z0-9-]+)/); + + if (tokenFound) { + return url.replace(tokenFound[0], ''); + } + + return url; +} + +$('form').validator({ + custom: { + 'validation-url-contains': function($el) { + return urlRegex.test($el.val()); + }, + 'validation-copyright-text': function($el) { + var value = $el.val().trim(); + + return !(value && value.length > 4 && yearRegex.test(value)); + }, + 'validation-version-number': function($el) { + var oldVersion = $el.data('validation-version-number'); + var newVersion = $el.val(); + var versionRegExp = /^\d+\.\d+\.\d+$/; + + if (!oldVersion || !$el.val() || !versionRegExp.test(newVersion)) { + return false; + } + + var segmentedOldVersion = oldVersion.split('.'); + var segmentedNewVersion = newVersion.split('.'); + + for (var i = 0; i < segmentedNewVersion.length; i++) { + var a = parseInt(segmentedNewVersion[i], 10) || 0; + var b = parseInt(segmentedOldVersion[i], 10) || 0; + + if (a > b) { + return false; + } + + if (a < b) { + $el.attr('data-validation-version-number-error', 'Please make sure the version number is higher than ' + oldVersion); + + return true; + } + } + + $el.attr('data-validation-version-number-error', 'Please make sure the version number is higher than ' + oldVersion); + + return true; + }, + 'validation-version-number-type': function($el) { + var newVersion = $el.val(); + var versionRegExp = /^\d+\.\d+\.\d+$/; + + if (!versionRegExp.test(newVersion)) { + $el.attr('data-validation-version-number-type-error', 'Please make sure the app version is a number'); + + return true; + } + + return false; + }, + 'validation-authentication-key': function($el) { + var invalidCharacterRegExp = /\\n/; + var authKeyRegExp = /^(-----BEGIN\sPRIVATE\sKEY-----\n)(.|\n)+(\n-----END\sPRIVATE\sKEY-----)$/; + + if (!authKeyRegExp.test($el.val()) || invalidCharacterRegExp.test($el.val())) { + $el.attr('data-validation-authentication-key-error', 'Authentication Key invalid. Please make sure the format is correct.'); + + + $pushConfigurationSaveButton.addClass('disabled'); + + return true; + } + + $pushConfigurationSaveButton.removeClass('disabled'); + + return false; + } + } +}); + +/* ATTACH LISTENERS */ + +$('[name="fl-push-authKey"]').on('input', function(event) { + if (!$(event.target).val()) { + $pushConfigurationSaveButton.removeClass('disabled'); + } +}); + +$('[data-toggle="tooltip"]').tooltip({ + title: function() { + var tooltipText = $(this).text(); + + if (tooltipText.length < 41) { + return; + } + + return tooltipText; + }, + delay: { 'show': 500, 'hide': 300 } +}); + +$('[data-template="fl-store-releaseNotes"]').on('click', function(e) { + e.preventDefault(); + + $('[name=fl-store-releaseNotes]').val(defaultReleaseNotes); +}); + +$('[data-template="fl-store-revNotes"]').on('click', function(e) { + e.preventDefault(); + + $('[name=fl-store-revNotes]').focus().val(defaultReviewNotes); + $('[name=fl-store-revNotes]').parents('.form-group').removeClass('has-error'); + $('[name=fl-store-revNotes]').next('.with-errors').html(''); +}); + +$('.appStore-2fa-sms, .enterprise-2fa-sms').find('a').on('click', function(e) { + e.preventDefault(); + // Send SMS request via socket + toggleLoginForm(getCurrentLoginForm(), '2fa-waiting'); + + if (!socketClientId) { + toggleLoginForm(getCurrentLoginForm(), 'login'); + + return; + } + + socket.to(socketClientId).emit('aab.apple.login.2fa.sms'); +}); + +$('#fl-store-2fa-select, #fl-ent-2fa-select').on('change', function(e) { + // Send device selection via socket + toggleLoginForm(getCurrentLoginForm(), '2fa-waiting'); + + if (!socketClientId) { + toggleLoginForm(getCurrentLoginForm(), 'login'); + + return; + } + + socket.to(socketClientId).emit('aab.apple.login.2fa.device', e.target.value); +}); + +Fliplet().then(function() { + checkDemoUser(); +}); + +// After user blur from 'demo user' field we check again to make sure that the field is empty. +// If field is empty we remove required attribute. +$('#fl-store-revDemoUser').on('input', function(event) { + userInput = event.originalEvent.inputType || false; + + checkDemoUser(); +}); + +$('.2fa-code-store-button, .2fa-code-ent-button').on('click', function() { + var code = $(this).parents('.form-group').prev().find('.form-control').val(); + + if (!code) { + Fliplet.Modal.alert({ + message: 'You must enter the verification code to continue' + }); + + return; + } + + if (!socketClientId) { + toggleLoginForm(getCurrentLoginForm(), 'login'); + + return; + } + + toggleLoginForm(getCurrentLoginForm(), '2fa-verifying'); + socket.to(socketClientId).emit('aab.apple.login.2fa.code', code); +}); + +$('[name="fl-store-screenshots"]').on('change', function() { + switch ($(this).val()) { + case 'new': + _.forEach(screenshotRequirements, function(req) { + var $thumbContainer = $('.thumbs[data-type="' + req.type + '"]'); + + $thumbContainer.html(''); + + if (!req.screenshots.length) { + $thumbContainer.append(addNoScreenshotWarning(req)); + + return; + } + + _.forEach(_.take(req.screenshots, 4), function(thumb) { + $thumbContainer.append(addThumb(thumb)); + }); + }); + + $('[data-item="fl-store-screenshots-new"]').removeClass('hidden'); + $('[data-item="fl-store-screenshots-existing"]').addClass('hidden'); + break; + case 'existing': + $('.app-details-appStore .app-screenshots').removeClass('has-error'); + $('[data-item="fl-store-screenshots-existing"]').removeClass('hidden'); + $('[data-item="fl-store-screenshots-new-warning"]').addClass('hidden'); + $('[data-item="fl-store-screenshots-new"]').addClass('hidden'); + $('.screenshots-details-error').addClass('hidden'); + break; + default: + break; + } +}); + +$('[name="submissionType"]').on('change', function() { + var selectedOptionId = $(this).attr('id'); + + $('.fl-sb-panel').removeClass('show'); + $('.' + selectedOptionId).addClass('show'); + + Fliplet.Widget.autosize(); +}); + +$('[name="fl-store-credentials"]').on('change', function() { + var value = $(this).val(); + + if (value === 'useOwn') { + $('.fl-store-credential.indented-area').removeClass('hidden'); + } else { + $('.fl-store-credential.indented-area').addClass('hidden'); + } + + Fliplet.Widget.autosize(); +}); + +$('.fl-sb-appStore [change-bundleid], .fl-sb-enterprise [change-bundleid], .fl-sb-unsigned [change-bundleid]').on('click', function() { + Fliplet.Modal.confirm({ + message: 'Are you sure you want to change the unique Bundle ID?' + }).then(function(confirmed) { + if (!confirmed) { + return; + } + + $('.fl-bundleId-holder').addClass('hidden'); + $('.fl-bundleId-field').addClass('show'); + + Fliplet.Widget.autosize(); + }); +}); + +$('.panel-group') + .on('shown.bs.collapse', '.panel-collapse', function() { + Fliplet.Widget.autosize(); + + var $panel = $(this).closest('.panel'); + + if (!$panel || !$panel.offset()) { + return; + } + + Fliplet.Studio.emit('scrollOverlayTo', $panel.offset().top); + }) + .on('hidden.bs.collapse', '.panel-collapse', function() { + Fliplet.Widget.autosize(); + }); + +$('a[data-toggle="tab"]') + .on('shown.bs.tab', function() { + Fliplet.Widget.autosize(); + + if (socketClientId) { + socket.to(socketClientId).emit('aab.apple.login.2fa.cancel'); + } + }) + .on('hidden.bs.tab', function() { + Fliplet.Widget.autosize(); + }); + +$('[name="fl-store-keywords"]').on('tokenfield:createtoken', function(e) { + var currentValue = e.currentTarget.value.replace(/,\s+/g, ','); + var newValue = e.attrs.value; + var oldAndNew = currentValue + ',' + newValue; + + if (oldAndNew.length > 100) { + e.preventDefault(); + } +}); + +$('.redirectToSettings, [data-change-settings]').on('click', function(event) { + event.preventDefault(); + + saveProgressOnClose().then(function() { + Fliplet.Studio.emit('close-overlay', { + name: 'publish-apple' + }); + + Fliplet.Studio.emit('overlay', { + name: 'app-settings', + options: { + size: 'large', + title: 'App Settings', + section: 'appSettingsGeneral', + appId: Fliplet.Env.get('appId') + } + }); + }).catch(function(err) { + Fliplet.Modal.alert({ + message: Fliplet.parseError(err) + }); + }); +}); + +$(document).on('click', '[data-change-assets]', function(event) { + event.preventDefault(); + + saveProgressOnClose().then(function() { + Fliplet.Studio.emit('close-overlay', { + name: 'publish-apple' + }); + + Fliplet.Studio.emit('overlay', { + name: 'app-settings', + options: { + size: 'large', + title: 'App Settings', + section: 'launchAssets', + appId: Fliplet.Env.get('appId') + } + }); + }).catch(function(err) { + Fliplet.Modal.alert({ + message: Fliplet.parseError(err) + }); + }); +}); + +$('#appStoreConfiguration, #enterpriseConfiguration, #unsignedConfiguration').on('validated.bs.validator', function() { + checkGroupErrors(); + Fliplet.Widget.autosize(); +}); + +$('#appStoreConfiguration').validator().on('submit', function(event) { + if (!storeFeatures.public) { + Fliplet.Studio.emit('overlay', { + name: 'app-settings', + options: { + size: 'large', + title: 'App Settings', + appId: Fliplet.Env.get('appId'), + section: 'appBilling', + helpLink: 'https://help.fliplet.com/app-settings/' + } + }); + + Fliplet.Studio.emit('track-event', { + category: 'app_billing', + action: 'open', + context: 'apple_launch' + }); + + return; + } + + validateImageUrl(appIcon, $('.fl-sb-appStore .setting-app-icon.default'), $('.fl-sb-appStore .image-details-error')); + + var defaultSplashScreenData = { + 'url': $('[data-' + appStoreSubmission.data.submissionType.toLowerCase() + '-default-splash-url]').data(appStoreSubmission.data.submissionType.toLowerCase() + '-default-splash-url') + }; + + if (appSettings.splashScreen) { + validateImageUrl(appSettings.splashScreen.url, $('.fl-sb-unsigned .app-splash-screen'), $('.fl-sb-unsigned .splash-details-error')); + } + + if (defaultSplashScreenData.url) { + validateImageUrl(defaultSplashScreenData.url, $('.fl-sb-unsigned .app-splash-screen'), $('.fl-sb-unsigned .splash-details-error')); + } + + if ($('[name="fl-store-screenshots"]:checked').val() === 'new' + && (!hasFolders || _.some(screenshotRequirements, function(req) { + return !req.screenshots.length; + }))) { + showError($('.app-screenshots'), $('.screenshots-details-error')); + + return; + } + + if (_.includes(['fl-store-appDevLogin', 'fl-store-appDevPass'], document.activeElement.id)) { + // User submitted app store login form + $('.login-appStore-button').trigger('click'); + + return; + } + + if (document.activeElement.id === 'fl-store-2fa-code') { + // User submitted app store login form + $('.2fa-code-store-button').trigger('click'); + + return; + } + + if (event.isDefaultPrevented()) { + // Gives time to Validator to apply classes + setTimeout(checkGroupErrors, 0); + Fliplet.Modal.alert({ + message: 'Please fill in all the required information.' + }); + + return; + } + + event.preventDefault(); + + if (!isValidVersion($('[name="fl-store-versionNumber"]').val())) { + Fliplet.Modal.alert({ + message: ERRORS.INVALID_VERSION + }); + + return; + } + + if ($('[name="fl-store-screenshots"]:checked').val() === 'new' && !hasAllScreenshots) { + Fliplet.Modal.alert({ + message: 'You need to add screenshots before submitting' + }); + + return; + } + + if (!validateScreenshots()) { + return; + } + + if (mustReviewTos) { + Fliplet.Studio.emit('onMustReviewTos'); + + return; + } + + if (appInfo && appInfo.productionAppId) { + if (allAppData.indexOf('appStore') > -1) { + if (appStoreLoggedIn) { + var certificateKind = $('[name="fl-store-distribution"]:checked').val(); + + if (certificateKind === 'generate-file' && !appStoreCertificateCreated) { + Fliplet.Modal.alert({ + message: 'You need to generate a certificate before requesting a submission' + }); + + return; + } + + if (certificateKind === 'upload-file' && (!appStoreFileField.files || !appStoreFileField.files[0])) { + Fliplet.Modal.alert({ + message: 'You need to upload a certificate before requesting a submission' + }); + + return; + } + + var message = 'Are you sure you wish to update your published app?'; + + if (appStoreSubmission.status === 'started') { + message = 'Are you sure you wish to request your app to be published?'; + } + + Fliplet.Modal.confirm({ + message: message + }).then(function(confirmed) { + if (!confirmed) { + return; + } + + saveAppStoreData(true); + }); + } else { + Fliplet.Modal.alert({ + message: 'You need to login with your Apple developer account details.
    Select one option to provide use with a distribution certificate.' + }); + } + } else { + Fliplet.Modal.alert({ + message: 'Please configure your App Settings to contain the required information.' + }); + } + } else { + var initialHtml = $('.button-appStore-request').html(); + + $('.button-appStore-request').html('Please wait ' + spinner); + $('.button-appStore-request').prop('disabled', true); + + publishApp('appStore').catch(function() { + $('.button-appStore-request').html(initialHtml); + $('.button-appStore-request').prop('disabled', false); + }); + } + + // Gives time to Validator to apply classes + setTimeout(checkGroupErrors, 0); +}); + +$('#enterpriseConfiguration').validator().on('submit', function(event) { + if (!storeFeatures.private) { + Fliplet.Studio.emit('overlay', { + name: 'app-settings', + options: { + size: 'large', + title: 'App Settings', + appId: Fliplet.Env.get('appId'), + section: 'appBilling', + helpLink: 'https://help.fliplet.com/app-settings/' + } + }); + + Fliplet.Studio.emit('track-event', { + category: 'app_billing', + action: 'open', + context: 'apple_launch' + }); + + return; + } + + validateImageUrl(appIcon, $('.fl-sb-enterprise .setting-app-icon.default'), $('.fl-sb-enterprise .image-details-error')); + + var defaultSplashScreenData = { + 'url': $('[data-' + enterpriseSubmission.data.submissionType.toLowerCase() + '-default-splash-url]').data(enterpriseSubmission.data.submissionType.toLowerCase() + '-default-splash-url') + }; + + if (appSettings.splashScreen) { + validateImageUrl(appSettings.splashScreen.url, $('.fl-sb-unsigned .app-splash-screen'), $('.fl-sb-unsigned .splash-details-error')); + } + + if (defaultSplashScreenData.url) { + validateImageUrl(defaultSplashScreenData.url, $('.fl-sb-unsigned .app-splash-screen'), $('.fl-sb-unsigned .splash-details-error')); + } + + if (_.includes(['fl-ent-appDevLogin', 'fl-ent-appDevPass'], document.activeElement.id)) { + // User submitted enterprise login form + $('.login-enterprise-button').trigger('click'); + + return; + } + + if (document.activeElement.id === 'fl-ent-2fa-code') { + // User submitted app store login form + $('.2fa-code-ent-button').trigger('click'); + + return; + } + + if (event.isDefaultPrevented()) { + // Gives time to Validator to apply classes + setTimeout(checkGroupErrors, 0); + Fliplet.Modal.alert({ + message: 'Please fill in all the required information.' + }); + + return; + } + + event.preventDefault(); + + if (!isValidVersion($('[name="fl-ent-versionNumber"]').val())) { + Fliplet.Modal.alert({ + message: ERRORS.INVALID_VERSION + }); + + return; + } + + if (!enterpriseManual && !enterpriseLoggedIn) { + Fliplet.Modal.alert({ + message: 'Please log in with the Apple Developer Account or choose to enter the data manually.' + }); + + return; + } + + var credentialKind = $('[name="fl-ent-distribution"]:checked').val(); + + if (!enterpriseManual) { + if (credentialKind === 'generate-file' && !enterpriseCertificateCreated) { + Fliplet.Modal.alert({ + message: 'You need to generate a certificate before requesting a submission' + }); + + return; + } + + if (credentialKind === 'upload-file' && (!enterpriseFileField.files || !enterpriseFileField.files[0])) { + Fliplet.Modal.alert({ + message: 'You need to upload a certificate before requesting a submission' + }); + + return; + } + } + + if (mustReviewTos) { + Fliplet.Studio.emit('onMustReviewTos'); + + return; + } + + if (appInfo && appInfo.productionAppId) { + if (allAppData.indexOf('enterprise') > -1) { + var message = 'Are you sure you wish to update your published app?'; + + if (enterpriseSubmission.status === 'started') { + message = 'Are you sure you wish to request your app to be published?'; + } + + Fliplet.Modal.confirm({ + message: message + }).then(function(confirmed) { + if (!confirmed) { + return; + } + + saveEnterpriseData(true); + }); + } else { + Fliplet.Modal.alert({ + message: 'Please configure your App Settings to contain the required information.' + }); + } + } else { + var initialHtml = $('.button-enterprise-request').html(); + + $('.button-enterprise-request').html('Please wait ' + spinner); + $('.button-enterprise-request').prop('disabled', true); + + publishApp('enterprise').catch(function() { + $('.button-enterprise-request').html(initialHtml); + $('.button-enterprise-request').prop('disabled', false); + }); + } + + // Gives time to Validator to apply classes + setTimeout(checkGroupErrors, 0); +}); + +$('#unsignedConfiguration').validator().on('submit', function(event) { + if (!organizationIsPaying) { + Fliplet.Studio.emit('overlay', { + name: 'app-settings', + options: { + size: 'large', + title: 'App Settings', + appId: Fliplet.Env.get('appId'), + section: 'appBilling', + helpLink: 'https://help.fliplet.com/app-settings/' + } + }); + + Fliplet.Studio.emit('track-event', { + category: 'app_billing', + action: 'open', + context: 'apple_launch' + }); + + return; + } + + validateImageUrl(appIcon, $('.fl-sb-unsigned .setting-app-icon.default'), $('.fl-sb-unsigned .image-details-error')); + + var defaultSplashScreenData = { + 'url': $('[data-' + unsignedSubmission.data.submissionType.toLowerCase() + '-default-splash-url]').data(unsignedSubmission.data.submissionType.toLowerCase() + '-default-splash-url') + }; + + if (appSettings.splashScreen) { + validateImageUrl(appSettings.splashScreen.url, $('.fl-sb-unsigned .app-splash-screen'), $('.fl-sb-unsigned .splash-details-error')); + } + + if (defaultSplashScreenData.url) { + validateImageUrl(defaultSplashScreenData.url, $('.fl-sb-unsigned .app-splash-screen'), $('.fl-sb-unsigned .splash-details-error')); + } + + if (event.isDefaultPrevented()) { + // Gives time to Validator to apply classes + setTimeout(checkGroupErrors, 0); + Fliplet.Modal.alert({ + message: 'Please fill in all the required information.' + }); + + return; + } + + event.preventDefault(); + + if (!isValidVersion($('[name="fl-uns-versionNumber"]').val())) { + Fliplet.Modal.alert({ + message: ERRORS.INVALID_VERSION + }); + + return; + } + + if (mustReviewTos) { + Fliplet.Studio.emit('onMustReviewTos'); + + return; + } + + if (appInfo && appInfo.productionAppId) { + if (allAppData.indexOf('unsigned') > -1) { + var message = 'Are you sure you wish to update your published app?'; + + if (unsignedSubmission.status === 'started') { + message = 'Are you sure you wish to request your app to be published?'; + } + + Fliplet.Modal.confirm({ + message: message + }).then(function(confirmed) { + if (!confirmed) { + return; + } + + saveUnsignedData(true); + }); + } else { + Fliplet.Modal.alert({ + message: 'Please configure your App Settings to contain the required information.' + }); + } + } else { + var initialHtml = $('.button-unsigned-request').html(); + + $('.button-unsigned-request').html('Please wait ' + spinner); + $('.button-unsigned-request').prop('disabled', true); + + publishApp('unsigned').catch(function() { + $('.button-unsigned-request').html(initialHtml); + $('.button-unsigned-request').prop('disabled', false); + }); + } + + // Gives time to Validator to apply classes + setTimeout(checkGroupErrors, 0); +}); + +/* SAVE PROGRESS CLICK */ +$('[data-app-store-save]').on('click', function() { + saveAppStoreData(); +}); +$('[data-enterprise-save]').on('click', function() { + saveEnterpriseData(); +}); +$('[data-unsigned-save]').on('click', function() { + saveUnsignedData(); +}); +$('[data-push-save]').on('click', function() { + savePushData(); +}); + +/* Credentials and Certificates App Store */ +$('.login-appStore-button').on('click', function() { + var $this = $(this); + var devEmail = $('#fl-store-appDevLogin').val(); + var devPass = $('#fl-store-appDevPass').val(); + var emailError = $('#fl-store-appDevLogin').data('error'); + var passError = $('#fl-store-appDevPass').data('error'); + + // Remove errors + $('#fl-store-appDevLogin').parents('.form-group').removeClass('has-error has-danger'); + $('#fl-store-appDevLogin').next('.with-errors').html(''); + $('#fl-store-appDevPass').parents('.form-group').removeClass('has-error has-danger'); + $('#fl-store-appDevPass').next('.with-errors').html(''); + $this.nextAll('login-error').html(''); + + toggleLoginForm('app-store', 'logging-in'); + + if (devEmail === '') { + $('#fl-store-appDevLogin').parents('.form-group').addClass('has-error has-danger'); + $('#fl-store-appDevLogin').next('.with-errors').html(emailError); + toggleLoginForm('app-store', 'login'); + } + + if (devPass === '') { + $('#fl-store-appDevPass').parents('.form-group').addClass('has-error has-danger'); + $('#fl-store-appDevPass').next('.with-errors').html(passError); + toggleLoginForm('app-store', 'login'); + } + + if (devEmail !== '' && devPass !== '') { + setCredentials(appStoreSubmission.id, { + type: 'apple', + status: 'created', + email: devEmail, + password: devPass + }) + .then(function() { + return appStoreTeamSetup(devEmail, true); + }) + .catch(function(error) { + var message = 'Unable to log in'; + + if (Fliplet.parseError(error)) { + message += '
    ' + Fliplet.parseError(error) + '
    '; + } + + Fliplet.Modal.hideAll(); + Fliplet.Modal.alert({ + message: message, + size: 'small' + }); + + toggleLoginForm('app-store', 'login'); + }); + } +}); + +$('.log-out-appStore').on('click', function() { + clearAppStoreCredentials(); +}); + +$('#fl-ent-certificate-manual-details').on('change', function() { + checkFileExtension(this.files[0].name, $('#fl-ent-certificate-manual-details-label'), '.p12'); +}); + +$('#fl-ent-mobileprovision-manual-details').on('change', function() { + checkFileExtension(this.files[0].name, $('#fl-ent-mobileprovision-manual-details-label'), '.mobileprovision'); +}); + +$('[name="fl-store-distribution"]').on('change', function() { + var value = $(this).val(); + + $('#fl-store-teams').prop('required', true); + + if (value === 'previous-file') { + if (appStoreCertificateReplaced) { + $('.appStore-previous-file-success').addClass('show'); + } + + $('.appStore-generate-file, .appStore-generate-file-success, .appStore-upload-file').removeClass('show'); + $('#fl-store-certificate').prop('required', false); + } + + if (value === 'generate-file') { + if (appStoreCertificateCreated) { + $('.appStore-generate-file-success').addClass('show'); + } else { + $('.appStore-generate-file').addClass('show'); + } + + $('.appStore-previous-file-success, .appStore-upload-file').removeClass('show'); + $('#fl-store-certificate').prop('required', false); + } + + if (value === 'upload-file') { + $('.appStore-upload-file').addClass('show'); + $('.appStore-previous-file-success, .appStore-generate-file, .appStore-generate-file-success').removeClass('show'); + + $('#fl-store-certificate').prop('required', true); + } + Fliplet.Widget.autosize(); }); +$('#fl-load-store-teams').on('click', function(e) { + e.preventDefault(); + + var $button = $(this); + var initialLabel = $button.html(); + var email = $('.appStore-logged-email').text(); + + $button.html('Loading ' + spinner).addClass('disabled'); + loadAppStoreTeams(email) + .then(function() { + $button.html(initialLabel).removeClass('disabled'); + toggleLoginForm('app-store', 'logged-in', { email: email }); + }) + .catch(function(error) { + $button.html(initialLabel).removeClass('disabled'); + + var message = 'Unable to load teams'; + + if (Fliplet.parseError(error)) { + message += '
    ' + Fliplet.parseError(error) + '
    '; + } + + Fliplet.Modal.hideAll(); + Fliplet.Modal.alert({ + message: message, + size: 'small' + }); + + if (error && Fliplet.parseError(error).match(/^The email or password you entered .+ are wrong/)) { + clearAppStoreCredentials(); + } + }); +}); + $('#fl-store-teams').on('change', function() { var value = $(this).val(); - var teamName = value ? $('#fl-store-teams').find(":selected").data('team-name') : ''; + var teamName = value ? $('#fl-store-teams').find(':selected').data('team-name') : ''; if (value !== '') { $('.appStore-more-options').addClass('show'); @@ -1577,30 +3680,38 @@ $('#fl-store-teams').on('change', function() { } var devEmail = $('#fl-store-appDevLogin').val(); - var devPass = $('#fl-store-appDevPass').val(); - return refreshAppStoreOptions(devEmail, devPass, value, teamName); + return refreshAppStoreOptions(devEmail, value, teamName); }); $('.appStore-generate-cert').on('click', function() { var $this = $(this); - $(this).html('Generating...'); + + $(this).html('Generating ' + spinner); $(this).addClass('disabled'); $('.generate-error').html(''); // Cleans errors + var teamId = $('#fl-store-teams').val(); - appStoreTeamId = teamId; - var teamName = $('#fl-store-teams').find(":selected").data('team-name'); + var teamName = $('#fl-store-teams').find(':selected').data('team-name'); - return setCredentials(organizationID, appStoreSubmission.id, { - teamId: teamId, - teamName: teamName - }) + return setCredentials(appStoreSubmission.id, { + teamId: teamId, + teamName: teamName + }) .then(function() { - return createCertificates(organizationID, appStoreSubmission.id) + return createCertificates({ + organizationId: organizationId, + submissionId: appStoreSubmission.id + }) .then(function(response) { + var p12Url = Fliplet.Env.get('apiUrl') + 'v1/organizations/' + organizationId + '/credentials/submission-' + appStoreSubmission.id + '/download/p12'; + var certUrl = Fliplet.Env.get('apiUrl') + 'v1/organizations/' + organizationId + '/credentials/submission-' + appStoreSubmission.id + '/download/certificate'; + appStoreCertificateCreated = true; $('.appStore-generate-file-success').find('.appStore-file-name-success').html(response.certificate.name); $('.appStore-generate-file-success').find('.appStore-file-expire-success').html(moment(response.certificate.expiresAt).format('MMMM Do YYYY')); + $('.appStore-generate-file-success').find('.appStore-file-download-key').attr('href', p12Url); + $('.appStore-generate-file-success').find('.appStore-file-download-cert').attr('href', certUrl); $('.appStore-generate-file').removeClass('show'); $('.appStore-generate-file-success').addClass('show'); $this.html('Generate certificate'); @@ -1610,43 +3721,108 @@ $('.appStore-generate-cert').on('click', function() { .catch(function(error) { $this.html('Generate certificate'); $this.removeClass('disabled'); - if (error.responseJSON.message) { - $('.generate-error').html(error.responseJSON.message); - } + console.log(error); + $('.generate-error').html(Fliplet.parseError(error)); }); }); -$('#fl-store-certificate').on('change', function() { - appStoreFileField = this; - var fileName = appStoreFileField.value.replace(/\\/g, '/').replace(/.*\//, ''); +$('#fl-store-certificate').on('change', function() { + appStoreFileField = this; + + var fileName = appStoreFileField.value.replace(/\\/g, '/').replace(/.*\//, ''); + + if (appStoreFileField.files && appStoreFileField.files[0]) { + $('#fl-store-certificate-label').val(fileName); + } +}); + +// Firebase + +$('#fl-store-firebase').on('change', function() { + var fileName = this.value.replace(/\\/g, '/').replace(/.*\//, ''); + var fileExtension = checkFileExtension(fileName, this, '.plist'); + + if (!fileExtension) { + $('#fl-store-firebase-uploaded').html('').addClass('hidden'); + + return; + } + + appStoreFirebaseFileField = this; + + if (this.files && this.files[0]) { + $('#fl-store-firebase-status').html('Enabled').addClass('analytics-success'); + $('#fl-store-firebase-uploaded').html('File uploaded: ' + fileName + '').removeClass('hidden'); + } +}); + +$('#fl-ent-firebase').on('change', function() { + var fileName = this.value.replace(/\\/g, '/').replace(/.*\//, ''); + var fileExtension = checkFileExtension(fileName, this, '.plist'); + + if (!fileExtension) { + $('#fl-ent-firebase-uploaded').html('').addClass('hidden'); + + return; + } + + enterpriseFirebaseFileField = this; + + if (this.files && this.files[0]) { + $('#fl-ent-firebase-status').html('Enabled').addClass('analytics-success'); + $('#fl-ent-firebase-uploaded').html('File uploaded: ' + fileName + '').removeClass('hidden'); + } +}); + +$('#fl-uns-firebase').on('change', function() { + var fileName = this.value.replace(/\\/g, '/').replace(/.*\//, ''); + var fileExtension = checkFileExtension(fileName, this, '.plist'); - if (appStoreFileField.files && appStoreFileField.files[0]) { - $('#fl-store-certificate-label').val(fileName); + if (!fileExtension) { + $('#fl-uns-firebase-uploaded').html('').addClass('hidden'); + + return; + } + + unsignedFirebaseFileField = this; + + if (this.files && this.files[0]) { + $('#fl-uns-firebase-status').html('Enabled').addClass('analytics-success'); + $('#fl-uns-firebase-uploaded').html('File uploaded: ' + fileName + '').removeClass('hidden'); } }); $('.appStore-replace-cert').on('click', function() { var $this = $(this); - $(this).html('Replacing...'); + + $(this).html('Replacing ' + spinner); $(this).addClass('disabled'); $('.replace-error').html(''); // Cleans errors + var teamId = appStorePreviousCredential ? appStorePreviousCredential.teamId : ''; - appStoreTeamId = teamId; var teamName = appStorePreviousCredential ? appStorePreviousCredential.teamName : ''; if (appStorePreviousCredential.certificate && appStorePreviousCredential.certificate.id) { - return revokeCertificate(organizationID, appStoreSubmission.id, appStorePreviousCredential.certificate.id) + return revokeCertificate(appStoreSubmission.id, appStorePreviousCredential.certificate.id) .then(function() { - return setCredentials(organizationID, appStoreSubmission.id, { - teamId: teamId, - teamName: teamName - }) + return setCredentials(appStoreSubmission.id, { + teamId: teamId, + teamName: teamName + }) .then(function() { - return createCertificates(organizationID, appStoreSubmission.id) + return createCertificates({ + organizationId: organizationId, + submissionId: appStoreSubmission.id + }) .then(function(response) { + var p12Url = Fliplet.Env.get('apiUrl') + 'v1/organizations/' + organizationId + '/credentials/submission-' + appStoreSubmission.id + '/download/p12'; + var certUrl = Fliplet.Env.get('apiUrl') + 'v1/organizations/' + organizationId + '/credentials/submission-' + appStoreSubmission.id + '/download/certificate'; + appStoreCertificateReplaced = true; $('.appStore-previous-file-success').find('.appStore-file-name-success').html(response.certificate.name); $('.appStore-previous-file-success').find('.appStore-file-expire-success').html(moment(response.certificate.expiresAt).format('MMMM Do YYYY')); + $('.appStore-previous-file-success').find('.appStore-file-download-key').attr('href', p12Url); + $('.appStore-previous-file-success').find('.appStore-file-download-cert').attr('href', certUrl); $('.appStore-previous-file-success').addClass('show'); $this.html('Replace certificate'); $this.removeClass('disabled'); @@ -1656,27 +3832,23 @@ $('.appStore-replace-cert').on('click', function() { .catch(function(error) { $this.html('Replace certificate'); $this.removeClass('disabled'); - if (error.responseJSON.message) { - $('.replace-error').html(error.responseJSON.message); - } + console.log(error); + $('.replace-error').html(Fliplet.parseError(error)); }); - } else { - $this.html('Replace certificate'); - $this.removeClass('disabled'); - $('.replace-error').html("We could not replace the certificate.\nPlease log into your https://developer.apple.com/account/ and revoke the certificate and create a new one using Fliplet."); - } + } + $this.html('Replace certificate'); + $this.removeClass('disabled'); + $('.replace-error').html('We could not replace the certificate.\nPlease log into your https://developer.apple.com/account/ and revoke the certificate and create a new one using Fliplet.'); }); /**/ /* Credentials and Certificates Enterprise */ $('.login-enterprise-button').on('click', function() { var $this = $(this); - $(this).html('Logging in...'); - $(this).addClass('disabled'); var devEmail = $('#fl-ent-appDevLogin').val(); var devPass = $('#fl-ent-appDevPass').val(); - var emailError = $('#fl-ent-distributionnt-appDevLogin').data('error'); + var emailError = $('#fl-ent-appDevLogin').data('error'); var passError = $('#fl-ent-appDevPass').data('error'); // Remove errors @@ -1686,81 +3858,59 @@ $('.login-enterprise-button').on('click', function() { $('#fl-ent-appDevPass').next('.with-errors').html(''); $this.nextAll('login-error').html(''); + toggleLoginForm('enterprise', 'logging-in'); + if (devEmail === '') { $('#fl-ent-appDevLogin').parents('.form-group').addClass('has-error has-danger'); $('#fl-ent-appDevLogin').next('.with-errors').html(emailError); - $(this).html('Log in'); - $(this).removeClass('disabled'); - Fliplet.Widget.autosize(); + toggleLoginForm('enterprise', 'login'); } if (devPass === '') { $('#fl-ent-appDevPass').parents('.form-group').addClass('has-error has-danger'); $('#fl-ent-appDevPass').next('.with-errors').html(passError); - $(this).html('Log in'); - $(this).removeClass('disabled'); - Fliplet.Widget.autosize(); + toggleLoginForm('enterprise', 'login'); } if (devEmail !== '' && devPass !== '') { - setCredentials(organizationID, enterpriseSubmission.id, { + setCredentials(enterpriseSubmission.id, { type: 'apple-enterprise', status: 'created', email: devEmail, password: devPass }) - .then(function() { - $('[name="fl-ent-distribution"][value="generate-file"]').prop('checked', true).trigger('change'); - - return getTeams(organizationID, enterpriseSubmission.id) - .then(function(teams) { - var enterpriseTeams = _.filter(teams, function(team) { - return team.type === "In-House"; - }) - enterpriseTeams.forEach(function(team, i) { - $('.enterprise-team').append(''); - }); + .then(function() { + return enterpriseTeamSetup(devEmail, true); + }) + .then(function() { + $('[name="fl-ent-distribution"][value="generate-file"]').prop('checked', true).trigger('change'); + }) + .catch(function(error) { + var message = 'Unable to log in'; - $this.html('Log in'); - $this.removeClass('disabled'); - $('.enterprise-logged-emai').html(devEmail); - $('.enterprise-login-details').addClass('hidden'); - $('.enterprise-logged-in, .enterprise-more-options, .enterprise-teams').addClass('show'); - enterpriseLoggedIn = true; - var teamId = $('#fl-ent-teams').val(); - var teamName = teamId ? $('#fl-ent-teams').find(":selected").data('team-name') : ''; - - if(teamId) { - $('.enterprise-more-options').addClass('show'); - } else { - $('.enterprise-more-options').removeClass('show'); - } + if (Fliplet.parseError(error)) { + message += '
    ' + Fliplet.parseError(error) + '
    '; + } - return refreshAppEnterpriseOptions(devEmail, devPass, teamId, teamName); + Fliplet.Modal.hideAll(); + Fliplet.Modal.alert({ + message: message, + size: 'small' }); - }) - .catch(function(error) { - if (error.responseJSON) { - $this.nextAll('.login-error').html(error.responseJSON.message); - } - $this.html('Log in'); - $this.removeClass('disabled'); - Fliplet.Widget.autosize(); - }); + + toggleLoginForm('enterprise', 'login'); + }); } }); $('.log-out-enterprise').on('click', function() { - enterpriseLoggedIn = false; - $('.enterprise-logged-emai').html(''); - $('.enterprise-login-details').removeClass('hidden'); - $('.enterprise-logged-in, .enterprise-more-options, .enterprise-teams').removeClass('show'); + clearEnterpriseCredentials(); }); $('[name="fl-ent-distribution"]').on('change', function() { var value = $(this).val(); - $('#fl-ent-teams').prop('required',true); + $('#fl-ent-teams').prop('required', true); if (value === 'previous-file') { if (enterpriseCertificateReplaced) { @@ -1769,26 +3919,62 @@ $('[name="fl-ent-distribution"]').on('change', function() { $('.enterprise-generate-file, .enterprise-generate-file-success, .enterprise-upload-file').removeClass('show'); } + if (value === 'generate-file') { if (enterpriseCertificateCreated) { $('.enterprise-generate-file-success').addClass('show'); } else { $('.enterprise-generate-file').addClass('show'); } + $('.enterprise-previous-file-success, .enterprise-upload-file').removeClass('show'); - $('#fl-ent-certificate').prop('required',false); + $('#fl-ent-certificate').prop('required', false); } + if (value === 'upload-file') { $('.enterprise-upload-file').addClass('show'); $('.enterprise-previous-file-success, .enterprise-generate-file, .enterprise-generate-file-success').removeClass('show'); - $('#fl-ent-certificate').prop('required',true); + $('#fl-ent-certificate').prop('required', true); } + Fliplet.Widget.autosize(); }); +$('#fl-load-ent-teams').on('click', function(e) { + e.preventDefault(); + + var $button = $(this); + var initialLabel = $button.html(); + + $button.html('Loading ' + spinner).addClass('disabled'); + loadEnterpriseTeams($('.enterprise-logged-email').text()) + .then(function() { + $button.html(initialLabel).removeClass('disabled'); + }) + .catch(function(error) { + $button.html(initialLabel).removeClass('disabled'); + + var message = 'Unable to load teams'; + + if (Fliplet.parseError(error)) { + message += '
    ' + Fliplet.parseError(error) + '
    '; + } + + Fliplet.Modal.hideAll(); + Fliplet.Modal.alert({ + message: message, + size: 'small' + }); + + if (error && Fliplet.parseError(error).match(/^The email or password you entered .+ are wrong/)) { + clearEnterpriseCredentials(); + } + }); +}); + $('#fl-ent-teams').on('change', function() { var value = $(this).val(); - var teamName = value ? $('#fl-ent-teams').find(":selected").data('team-name') : ''; + var teamName = value ? $('#fl-ent-teams').find(':selected').data('team-name') : ''; if (value !== '') { $('.enterprise-more-options').addClass('show'); @@ -1801,524 +3987,222 @@ $('#fl-ent-teams').on('change', function() { } var devEmail = $('#fl-ent-appDevLogin').val(); - var devPass = $('#fl-ent-appDevPass').val(); - - return refreshAppEnterpriseOptions(devEmail, devPass, value, teamName); + return refreshAppEnterpriseOptions(devEmail, value, teamName); }); $('.enterprise-generate-cert').on('click', function() { var $this = $(this); - $(this).html('Generating...'); + + $(this).html('Generating ' + spinner); $(this).addClass('disabled'); $('.generate-error').html(''); // Cleans errors - var teamId = $('#fl-ent-teams').val(); - enterpriseTeamId = teamId; - var teamName = $('#fl-ent-teams').find(":selected").data('team-name'); - - return setCredentials(organizationID, enterpriseSubmission.id, { - teamId: teamId, - teamName: teamName - }) - .then(function() { - return createCertificates(organizationID, enterpriseSubmission.id) - .then(function(response) { - enterpriseCertificateCreated = true; - $('.enterprise-generate-file-success').find('.enterprise-file-name-success').html(response.certificate.name); - $('.enterprise-generate-file-success').find('.enterprise-file-expire-success').html(moment(response.certificate.expiresAt).format('MMMM Do YYYY')); - $('.enterprise-generate-file').removeClass('show'); - $('.enterprise-generate-file-success').addClass('show'); - $this.html('Generate certificate'); - $this.removeClass('disabled'); - }); - }) - .catch(function(error) { - $this.html('Generate certificate'); - $this.removeClass('disabled'); - if (error.responseJSON.message) { - $('.generate-error').html(error.responseJSON.message); - } - }); -}); - -$('#fl-ent-certificate').on('change', function() { - enterpriseFileField = this; - var fileName = enterpriseFileField.value.replace(/\\/g, '/').replace(/.*\//, ''); - - if (enterpriseFileField.files && enterpriseFileField.files[0]) { - $('#fl-ent-certificate-label').val(fileName); - } -}); - -$('#fl-ent-certificate-manual-details').on('change', function() { - enterpriseFileFieldManual = this; - var fileName = enterpriseFileFieldManual.value.replace(/\\/g, '/').replace(/.*\//, ''); - - if (enterpriseFileFieldManual.files && enterpriseFileFieldManual.files[0]) { - $('#fl-ent-certificate-manual-details-label').val(fileName); - } -}); - -$('#fl-ent-mobileprovision-manual-details').on('change', function() { - enterpriseFileProvisionFieldManual = this; - var fileName = enterpriseFileProvisionFieldManual.value.replace(/\\/g, '/').replace(/.*\//, ''); - if (enterpriseFileProvisionFieldManual.files && enterpriseFileProvisionFieldManual.files[0]) { - $('#fl-ent-mobileprovision-manual-details-label').val(fileName); - } -}); + var teamId = $('#fl-ent-teams').val(); -$('.enterprise-replace-cert').on('click', function() { - var $this = $(this); - $(this).html('Replacing...'); - $(this).addClass('disabled'); - $('.replace-error').html(''); // Cleans errors - var teamId = enterprisePreviousCredential ? enterprisePreviousCredential.teamId : ''; enterpriseTeamId = teamId; - var teamName = enterprisePreviousCredential ? enterprisePreviousCredential.teamName : ''; - - if (enterprisePreviousCredential.certificate && enterprisePreviousCredential.certificate.id) { - return revokeCertificate(organizationID, enterpriseSubmission.id, enterprisePreviousCredential.certificate.id) - .then(function() { - return setCredentials(organizationID, enterpriseSubmission.id, { - teamId: teamId, - teamName: teamName - }) - .then(function() { - return createCertificates(organizationID, enterpriseSubmission.id) - .then(function(response) { - enterpriseCertificateReplaced = true; - $('.enterprise-previous-file-success').find('.enterprise-file-name-success').html(response.certificate.name); - $('.enterprise-previous-file-success').find('.enterprise-file-expire-success').html(moment(response.certificate.expiresAt).format('MMMM Do YYYY')); - $('.enterprise-previous-file-success').addClass('show'); - $this.html('Replace certificate'); - $this.removeClass('disabled'); - }); - }); - }) - .catch(function(error) { - $this.html('Replace certificate'); - $this.removeClass('disabled'); - if (error.responseJSON.message) { - $('.replace-error').html(error.responseJSON.message); - } - }); - } else { - $this.html('Replace certificate'); - $this.removeClass('disabled'); - $('.replace-error').html("We could not replace the certificate.\nPlease log into your https://developer.apple.com/account/ and revoke the certificate and create a new one using Fliplet."); - } -}); - -$('.ent-enter-manually').on('click', function() { - $('.enterprise-login-details').addClass('hidden'); - $('.enterprise-manual-details').addClass('show'); - enterpriseManual = true; - - $('#fl-ent-appDevLogin').prop('required', false); - $('#fl-ent-appDevPass').prop('required', false); - $('#fl-ent-teams').prop('required',false); - - $('#fl-ent-teamName').prop('required', true); - $('#fl-ent-teamId').prop('required', true); - $('#fl-ent-certificate-manual-details').prop('required', true); - $('#fl-ent-mobileprovision-manual-details').prop('required', true); -}); -$('.enterprise-back-login').on('click', function() { - $('.enterprise-login-details').removeClass('hidden'); - $('.enterprise-manual-details').removeClass('show'); - enterpriseManual = false; - - $('#fl-ent-appDevLogin').prop('required', true); - $('#fl-ent-appDevPass').prop('required', true); - $('#fl-ent-teams').prop('required',true); - - $('#fl-ent-teamName').prop('required', false); - $('#fl-ent-teamId').prop('required', false); - $('#fl-ent-certificate-manual-details').prop('required', false); - $('#fl-ent-mobileprovision-manual-details').prop('required', false); -}); -/**/ - -$(document).on('click', '[data-cancel-build-id]', function() { - var buildId = $(this).data('cancel-build-id'); + var teamName = $('#fl-ent-teams').find(':selected').data('team-name'); - Fliplet.API.request({ - method: 'DELETE', - url: 'v1/apps/' + Fliplet.Env.get('appId') + '/submissions/' + buildId + return setCredentials(enterpriseSubmission.id, { + teamId: teamId, + teamName: teamName }) - .then(function() { - clearTimeout(initLoad); - initialLoad(false, 0); - }) -}); - -/* INIT */ -$('#appStoreConfiguration, #enterpriseConfiguration, #unsignedConfiguration').validator().off('change.bs.validator focusout.bs.validator'); -$('[name="submissionType"][value="appStore"]').prop('checked', true).trigger('change'); - -function compileStatusTable(withData, origin, buildsData) { - if (withData) { - var template = Handlebars.compile(statusTableTemplate); - var html = template(buildsData); - - if (origin === "appStore") { - $statusAppStoreTableElement.html(html); - } - if (origin === "enterprise") { - $statusEnterpriseTableElement.html(html); - } - if (origin === "unsigned") { - $statusUnsignedTableElement.html(html); - } - } else { - if (origin === "appStore") { - $statusAppStoreTableElement.html(''); - } - if (origin === "enterprise") { - $statusEnterpriseTableElement.html(''); - } - if (origin === "unsigned") { - $statusUnsignedTableElement.html(''); - } - } - - Fliplet.Widget.autosize(); -} - -function checkSubmissionStatus(origin, iosSubmissions) { - var submissionsToShow = _.filter(iosSubmissions, function(submission) { - return submission.status === "queued" || submission.status === "submitted" || submission.status === "processing" || submission.status === "completed" || submission.status === "failed" || submission.status === "cancelled" || submission.status === "ready-for-testing" || submission.status === "tested"; - }); - - var buildsData = []; - if (submissionsToShow.length) { - submissionsToShow.forEach(function(submission) { - var build = {}; - var appBuild; - var debugHtmlPage; - - if (submission.result.appBuild && submission.result.appBuild.files) { - appBuild = _.find(submission.result.appBuild.files, function(file) { - var dotIndex = file.url.lastIndexOf('.'); - var ext = file.url.substring(dotIndex); - if (ext === '.ipa') { - return true; - } - }); - } else if (submission.data.previousResults && submission.data.previousResults.appBuild && submission.data.previousResults.appBuild.files) { - appBuild = _.find(submission.data.previousResults.appBuild.files, function(file) { - var dotIndex = file.url.lastIndexOf('.'); - var ext = file.url.substring(dotIndex); - if (ext === '.ipa') { - return true; - } - }); - } - - if (submission.result.debugHtmlPage && submission.result.debugHtmlPage.files) { - debugHtmlPage = _.find(submission.result.debugHtmlPage.files, function(file) { - var dotIndex = file.url.lastIndexOf('.'); - var ext = file.url.substring(dotIndex); - if (ext === '.html') { - return true; - } - }); - } else if (submission.data.previousResults && submission.data.previousResults.debugHtmlPage && submission.data.previousResults.debugHtmlPage.files) { - debugHtmlPage = _.find(submission.data.previousResults.debugHtmlPage.files, function(file) { - var dotIndex = file.url.lastIndexOf('.'); - var ext = file.url.substring(dotIndex); - if (ext === '.html') { - return true; - } - }); - } - - build.id = submission.id; - build.updatedAt = ((submission.status === 'completed' || submission.status === 'failed' || submission.status === 'cancelled' || submission.status === 'ready-for-testing' || submission.status === 'tested') && submission.updatedAt) ? - moment(submission.updatedAt).format('MMM Do YYYY, h:mm:ss a') : - ''; - build.submittedAt = ((submission.status === 'queued' || submission.status === 'submitted') && submission.submittedAt) ? - moment(submission.submittedAt).format('MMM Do YYYY, h:mm:ss a') : - ''; - build[submission.status] = true; - build.fileUrl = appBuild ? appBuild.url : ''; - - if (userInfo.user && (userInfo.user.isAdmin || userInfo.user.isImpersonating)) { - build.debugFileUrl = debugHtmlPage ? debugHtmlPage.url : ''; - } - - buildsData.push(build); - }); - - compileStatusTable(true, origin, buildsData); - } else { - compileStatusTable(false, origin); - } -} + .then(function() { + return createCertificates({ + organizationId: organizationId, + submissionId: enterpriseSubmission.id, + inHouse: true + }) + .then(function(response) { + var p12Url = Fliplet.Env.get('apiUrl') + 'v1/organizations/' + organizationId + '/credentials/submission-' + enterpriseSubmission.id + '/download/p12'; + var certUrl = Fliplet.Env.get('apiUrl') + 'v1/organizations/' + organizationId + '/credentials/submission-' + enterpriseSubmission.id + '/download/certificate'; -function submissionChecker(submissions) { - var asub = _.filter(submissions, function(submission) { - return submission.data.submissionType === "appStore" && submission.platform === "ios"; - }); + enterpriseCertificateCreated = true; + $('.enterprise-generate-file-success').find('.enterprise-file-name-success').html(response.certificate.name); + $('.enterprise-generate-file-success').find('.enterprise-file-expire-success').html(moment(response.certificate.expiresAt).format('MMMM Do YYYY')); + $('.enterprise-generate-file-success').find('.enterprise-file-download-key').attr('href', p12Url); + $('.enterprise-generate-file-success').find('.enterprise-file-download-cert').attr('href', certUrl); + $('.enterprise-generate-file').removeClass('show'); + $('.enterprise-generate-file-success').addClass('show'); + $this.html('Generate certificate'); + $this.removeClass('disabled'); + }); + }) + .catch(function(error) { + $this.html('Generate certificate'); + $this.removeClass('disabled'); + console.log(error); + $('.generate-error').html(Fliplet.parseError(error)); + }); +}); - checkSubmissionStatus("appStore", asub); +$('#fl-ent-certificate').on('change', function() { + enterpriseFileField = this; - asub = _.maxBy(asub, function(el) { - return new Date(el.createdAt).getTime(); - }); - appStoreSubmission = asub; + var fileName = enterpriseFileField.value.replace(/\\/g, '/').replace(/.*\//, ''); - if (appStoreSubmission.data && !appStoreSubmission.data['fl-credentials']) { + if (enterpriseFileField.files && enterpriseFileField.files[0]) { + $('#fl-ent-certificate-label').val(fileName); + } +}); - var prevSubCred = _.filter(submissions, function(submission) { - return submission.data && submission.data['fl-credentials']; - }); +$('#fl-ent-certificate-manual-details').on('change', function() { + enterpriseFileFieldManual = this; - var previousSubWithCredentials = _.maxBy(prevSubCred, function(el) { - return new Date(el.createdAt).getTime(); - }); + var fileName = enterpriseFileFieldManual.value.replace(/\\/g, '/').replace(/.*\//, ''); - if(previousSubWithCredentials) { - appStoreSubmission.data['fl-credentials'] = previousSubWithCredentials.data['fl-credentials']; - } - else { - appStoreSubmission.data['fl-credentials'] = ''; - } + if (enterpriseFileFieldManual.files && enterpriseFileFieldManual.files[0]) { + $('#fl-ent-certificate-manual-details-label').val(fileName); } +}); - var esub = _.filter(submissions, function(submission) { - return submission.data.submissionType === "enterprise" && submission.platform === "ios"; - }); +$('#fl-ent-mobileprovision-manual-details').on('change', function() { + enterpriseFileProvisionFieldManual = this; - checkSubmissionStatus("enterprise", esub); + var fileName = enterpriseFileProvisionFieldManual.value.replace(/\\/g, '/').replace(/.*\//, ''); - esub = _.maxBy(esub, function(el) { - return new Date(el.createdAt).getTime(); - }); - enterpriseSubmission = esub; + if (enterpriseFileProvisionFieldManual.files && enterpriseFileProvisionFieldManual.files[0]) { + $('#fl-ent-mobileprovision-manual-details-label').val(fileName); + } +}); - if (enterpriseSubmission.data && !enterpriseSubmission.data['fl-credentials']) { +$('.enterprise-replace-cert').on('click', function() { + var $this = $(this); - var prevSubCred = _.filter(submissions, function(submission) { - return submission.data && submission.data['fl-credentials']; - }); + $(this).html('Replacing ' + spinner); + $(this).addClass('disabled'); + $('.replace-error').html(''); // Cleans errors - var previousSubWithCredentials = _.maxBy(prevSubCred, function(el) { - return new Date(el.createdAt).getTime(); - }); + var teamId = enterprisePreviousCredential ? enterprisePreviousCredential.teamId : ''; - if(previousSubWithCredentials) { - enterpriseSubmission.data['fl-credentials'] = previousSubWithCredentials.data['fl-credentials']; - } - else { - enterpriseSubmission.data['fl-credentials'] = ''; - } - } + enterpriseTeamId = teamId; - var usub = _.filter(submissions, function(submission) { - return submission.data.submissionType === "unsigned" && submission.platform === "ios"; - }); + var teamName = enterprisePreviousCredential ? enterprisePreviousCredential.teamName : ''; - checkSubmissionStatus("unsigned", usub); + if (enterprisePreviousCredential.certificate && enterprisePreviousCredential.certificate.id) { + return revokeCertificate(enterpriseSubmission.id, enterprisePreviousCredential.certificate.id) + .then(function() { + return setCredentials(enterpriseSubmission.id, { + teamId: teamId, + teamName: teamName + }) + .then(function() { + return createCertificates({ + organizationId: organizationId, + submissionId: enterpriseSubmission.id, + inHouse: true + }) + .then(function(response) { + var p12Url = Fliplet.Env.get('apiUrl') + 'v1/organizations/' + organizationId + '/credentials/submission-' + enterpriseSubmission.id + '/download/p12'; + var certUrl = Fliplet.Env.get('apiUrl') + 'v1/organizations/' + organizationId + '/credentials/submission-' + enterpriseSubmission.id + '/download/certificate'; - usub = _.maxBy(usub, function(el) { - return new Date(el.createdAt).getTime(); - }); - unsignedSubmission = usub; + enterpriseCertificateReplaced = true; - if (_.isEmpty(appStoreSubmission)) { - Fliplet.App.Submissions.create({ - platform: 'ios', - data: { - submissionType: "appStore" - } + $('.enterprise-previous-file-success').find('.enterprise-file-name-success').html(response.certificate.name); + $('.enterprise-previous-file-success').find('.enterprise-file-expire-success').html(moment(response.certificate.expiresAt).format('MMMM Do YYYY')); + $('.enterprise-previous-file-success').find('.enterprise-file-download-key').attr('href', p12Url); + $('.enterprise-previous-file-success').find('.enterprise-file-download-cert').attr('href', certUrl); + $('.enterprise-previous-file-success').addClass('show'); + $this.html('Replace certificate'); + $this.removeClass('disabled'); + }); + }); }) - .then(function(submission) { - appStoreSubmission = submission; + .catch(function(error) { + $this.html('Replace certificate'); + $this.removeClass('disabled'); + console.log(error); + $('.replace-error').html(Fliplet.parseError(error)); }); } - if (_.isEmpty(enterpriseSubmission)) { - Fliplet.App.Submissions.create({ - platform: 'ios', - data: { - submissionType: "enterprise" - } - }) - .then(function(submission) { - enterpriseSubmission = submission; - }); - } + $this.html('Replace certificate'); + $this.removeClass('disabled'); + $('.replace-error').html('We could not replace the certificate.\nPlease log into your https://developer.apple.com/account/ and revoke the certificate and create a new one using Fliplet.'); +}); - if (_.isEmpty(unsignedSubmission)) { - Fliplet.App.Submissions.create({ - platform: 'ios', - data: { - submissionType: "unsigned" - } - }) - .then(function(submission) { - unsignedSubmission = submission; - }); - } -} +$('.ent-enter-manually').on('click', function() { + $('.enterprise-login-details').addClass('hidden'); + $('.enterprise-manual-details').addClass('show'); + enterpriseManual = true; -function iosSubmissionChecker(submissions) { - var asub = _.filter(submissions, function(submission) { - return submission.data.submissionType === "appStore" && submission.platform === "ios"; - }); + $('#fl-ent-appDevLogin').prop('required', false); + $('#fl-ent-appDevPass').prop('required', false); + $('#fl-ent-teams').prop('required', false); - var esub = _.filter(submissions, function(submission) { - return submission.data.submissionType === "enterprise" && submission.platform === "ios"; - }); + $('#fl-ent-teamName').prop('required', true); + $('#fl-ent-teamId').prop('required', true); + $('#fl-ent-certificate-manual-details').prop('required', true); + $('#fl-ent-mobileprovision-manual-details').prop('required', true); +}); - var usub = _.filter(submissions, function(submission) { - return submission.data.submissionType === "unsigned" && submission.platform === "ios"; - }); +$('.enterprise-back-login').on('click', function() { + $('.enterprise-login-details').removeClass('hidden'); + $('.enterprise-manual-details').removeClass('show'); + enterpriseManual = false; - checkSubmissionStatus("appStore", asub); - checkSubmissionStatus("enterprise", esub); - checkSubmissionStatus("unsigned", usub); -} + $('#fl-ent-appDevLogin').prop('required', true); + $('#fl-ent-appDevPass').prop('required', true); + $('#fl-ent-teams').prop('required', true); -function getSubmissions() { - return Fliplet.App.Submissions.get(); -} + $('#fl-ent-teamName').prop('required', false); + $('#fl-ent-teamId').prop('required', false); + $('#fl-ent-certificate-manual-details').prop('required', false); + $('#fl-ent-mobileprovision-manual-details').prop('required', false); +}); +/**/ -function initialLoad(initial, timeout) { - if (!initial) { - initLoad = setTimeout(function() { - getSubmissions() - .then(function(submissions) { - iosSubmissionChecker(submissions); - initialLoad(false, 15000); - }); - }, timeout); - } else { - getSubmissions() - .then(function(submissions) { +$(document).on('click', '[data-cancel-build-id]', function() { + var buildId = $(this).data('cancel-build-id'); - if (!submissions.length) { - return Promise.all([ - Fliplet.App.Submissions.create({ - platform: 'ios', - data: { - submissionType: "appStore" - } - }) - .then(function(submission) { - appStoreSubmission = submission; - }), - Fliplet.App.Submissions.create({ - platform: 'ios', - data: { - submissionType: "unsigned" - } - }) - .then(function(submission) { - unsignedSubmission = submission; - }), - Fliplet.App.Submissions.create({ - platform: 'ios', - data: { - submissionType: "enterprise" - } - }) - .then(function(submission) { - enterpriseSubmission = submission; - }) - ]); - } + Fliplet.API.request({ + method: 'DELETE', + url: 'v1/apps/' + Fliplet.Env.get('appId') + '/submissions/' + buildId + }) + .then(function() { + clearTimeout(initLoad); + initialLoad(false, 0); + }); +}); - return Fliplet.API.request({ - cache: true, - url: 'v1/user' - }) - .then(function(user) { - userInfo = user; - submissionChecker(submissions); - return Promise.resolve(); - }); - }) - .then(function() { - // Fliplet.Env.get('appId') - // Fliplet.Env.get('appName') - // Fliplet.Env.get('appSettings') +$('.browse-files').on('click', function(e) { + e.preventDefault(); + + Fliplet.Studio.emit('overlay', { + name: 'widget', + options: { + size: 'large', + package: 'com.fliplet.file-manager', + title: 'File Manager', + classes: 'data-source-overlay', + data: { + context: 'overlay', + appId: Fliplet.Env.get('appId') + } + } + }); +}); - return Promise.all([ - Fliplet.API.request({ - method: 'GET', - url: 'v1/apps/' + Fliplet.Env.get('appId') - }) - .then(function(result) { - appName = result.app.name; - appIcon = result.app.icon; - appSettings = result.app.settings; - }), - Fliplet.API.request({ - method: 'GET', - url: 'v1/organizations/' + Fliplet.Env.get('organizationId') - }) - .then(function(org) { - organizationName = org.name; - }) - ]); - }) - .then(function() { - if (appSettings.folderStructure) { - var structure = []; - hasFolders = true; - var appleOnly = _.filter(appSettings.folderStructure, function(obj) { - return obj.platform === 'apple'; - }); +// Listen for 2FA code when requested +socket.on('aab.apple.login.2fa', function(data) { + socketClientId = data.clientId || socketClientId; + // Ask user for code + toggleLoginForm(getCurrentLoginForm(), '2fa-code', data); +}); - return Promise.all(appleOnly.map((obj) => { - return Fliplet.Media.Folders.get({folderId: obj.folderId}) - .then(function(result) { - var tempObject = { - type: obj.type, - folderContent: result - } +socket.on('aab.apple.login.2fa.devices', function(data) { + socketClientId = data.clientId || socketClientId; + // Ask user for device + toggleLoginForm(getCurrentLoginForm(), '2fa-device', data); +}); - structure.push(tempObject); - return Promise.resolve(structure); - }); - })) - .then(function() { - structure.forEach(function(el, idx) { - if (el.type === 'mobile') { - screenShotsMobile = el.folderContent.files - } - if (el.type === 'tablet') { - screenShotsTablet = el.folderContent.files - } - }); - }); - } else { - hasFolders = false; - return; - } - }) - .then(function() { - return Fliplet.API.request({ - method: 'GET', - url: 'v1/widget-instances/com.fliplet.push-notifications?appId=' + Fliplet.Env.get('appId') - }); - }) - .then(function(response) { - if (response.widgetInstance.settings && response.widgetInstance.settings) { - notificationSettings = response.widgetInstance.settings; - } else { - notificationSettings = {}; - } +/* INIT */ +$('#appStoreConfiguration, #enterpriseConfiguration, #unsignedConfiguration').validator().off('change.bs.validator focusout.bs.validator'); +$('[name="submissionType"][value="appStore"]').prop('checked', true).trigger('change'); +$('.copyright-helper').html('e.g. ' + new Date().getFullYear() + ' Acme Inc.'); - init(); - initialLoad(false, 5000); - }); - } -} +updateServerLocation(); // Start -initLoad = initialLoad(true, 0); \ No newline at end of file +initLoad = initialLoad(true, 0); diff --git a/js/interface.templates.js b/js/interface.templates.js index 8167d6d..7a516e6 100644 --- a/js/interface.templates.js +++ b/js/interface.templates.js @@ -1,11 +1,44 @@ this["Fliplet"] = this["Fliplet"] || {}; this["Fliplet"]["Widget"] = this["Fliplet"]["Widget"] || {}; -this["Fliplet"]["Widget"]["Templates"] = this["Fliplet"]["Widget"]["Templates"] || {}; +this["Fliplet"]["Widget"]["Templates"] = this["Fliplet"]["Widget"]["Templates"] || {}; + +this["Fliplet"]["Widget"]["Templates"]["templates.no-thumb"] = Handlebars.template({"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) { + var helper; + + return "
    \r\n
    Apple requires you to upload up to 4 screenshots of your app for " + + container.escapeExpression(((helper = (helper = helpers.name || (depth0 != null ? depth0.name : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"name","hash":{},"data":data}) : helper))) + + ". To generate your screenshots, go to App Settings.
    \r\n
    "; +},"useData":true}); + +this["Fliplet"]["Widget"]["Templates"]["templates.thumb-containers"] = Handlebars.template({"1":function(container,depth0,helpers,partials,data) { + var stack1, helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression; + + return "
    \r\n

    " + + alias4(((helper = (helper = helpers.name || (depth0 != null ? depth0.name : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"name","hash":{},"data":data}) : helper))) + + "
    " + + ((stack1 = helpers.each.call(alias1,(depth0 != null ? depth0.sizes : depth0),{"name":"each","hash":{},"fn":container.program(2, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") + + "

    \r\n
    \r\n
    \r\n"; +},"2":function(container,depth0,helpers,partials,data) { + var stack1, alias1=container.lambda, alias2=container.escapeExpression; + + return ((stack1 = helpers.unless.call(depth0 != null ? depth0 : (container.nullContext || {}),(data && data.first),{"name":"unless","hash":{},"fn":container.program(3, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") + + alias2(alias1((depth0 != null ? depth0["0"] : depth0), depth0)) + + " × " + + alias2(alias1((depth0 != null ? depth0["1"] : depth0), depth0)); +},"3":function(container,depth0,helpers,partials,data) { + return " or "; +},"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) { + var stack1; + return ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),depth0,{"name":"each","hash":{},"fn":container.program(1, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : ""); +},"useData":true}); + this["Fliplet"]["Widget"]["Templates"]["templates.thumbs"] = Handlebars.template({"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) { var helper; return "
    \r\n \r\n
    "; + + "\" />\r\n"; },"useData":true}); \ No newline at end of file diff --git a/js/templates/no-thumb.interface.hbs b/js/templates/no-thumb.interface.hbs new file mode 100644 index 0000000..1211447 --- /dev/null +++ b/js/templates/no-thumb.interface.hbs @@ -0,0 +1,3 @@ +
    +
    Apple requires you to upload up to 4 screenshots of your app for {{ name }}. To generate your screenshots, go to App Settings.
    +
    \ No newline at end of file diff --git a/js/templates/thumb-containers.interface.hbs b/js/templates/thumb-containers.interface.hbs new file mode 100644 index 0000000..4fc9bd1 --- /dev/null +++ b/js/templates/thumb-containers.interface.hbs @@ -0,0 +1,6 @@ +{{#each this}} +
    +

    {{ name }}
    {{#each sizes}}{{#unless @first}} or {{/unless}}{{this.[0]}} × {{this.[1]}}{{/each}}

    +
    +
    +{{/each}} \ No newline at end of file diff --git a/js/templates/thumbs.interface.hbs b/js/templates/thumbs.interface.hbs index 09abe2c..79d3b70 100644 --- a/js/templates/thumbs.interface.hbs +++ b/js/templates/thumbs.interface.hbs @@ -1,3 +1,3 @@
    - +
    \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..63f05bd --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1164 @@ +{ + "requires": true, + "lockfileVersion": 1, + "dependencies": { + "@babel/code-frame": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", + "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", + "dev": true, + "requires": { + "@babel/highlight": "^7.14.5" + } + }, + "@babel/generator": { + "version": "7.15.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.15.0.tgz", + "integrity": "sha512-eKl4XdMrbpYvuB505KTta4AV9g+wWzmVBW69tX0H2NwKVKd2YJbKgyK6M8j/rgLbmHOYJn6rUklV677nOyJrEQ==", + "dev": true, + "requires": { + "@babel/types": "^7.15.0", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "@babel/helper-function-name": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.14.5.tgz", + "integrity": "sha512-Gjna0AsXWfFvrAuX+VKcN/aNNWonizBj39yGwUzVDVTlMYJMK2Wp6xdpy72mfArFq5uK+NOuexfzZlzI1z9+AQ==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.14.5", + "@babel/template": "^7.14.5", + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.14.5.tgz", + "integrity": "sha512-I1Db4Shst5lewOM4V+ZKJzQ0JGGaZ6VY1jYvMghRjqs6DWgxLCIyFt30GlnKkfUeFLpJt2vzbMVEXVSXlIFYUg==", + "dev": true, + "requires": { + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-hoist-variables": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.14.5.tgz", + "integrity": "sha512-R1PXiz31Uc0Vxy4OEOm07x0oSjKAdPPCh3tPivn/Eo8cvz6gveAeuyUUPB21Hoiif0uoPQSSdhIPS3352nvdyQ==", + "dev": true, + "requires": { + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.14.5.tgz", + "integrity": "sha512-hprxVPu6e5Kdp2puZUmvOGjaLv9TCe58E/Fl6hRq4YiVQxIcNvuq6uTM2r1mT/oPskuS9CgR+I94sqAYv0NGKA==", + "dev": true, + "requires": { + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.14.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.9.tgz", + "integrity": "sha512-pQYxPY0UP6IHISRitNe8bsijHex4TWZXi2HwKVsjPiltzlhse2znVcm9Ace510VT1kxIHjGJCZZQBX2gJDbo0g==", + "dev": true + }, + "@babel/highlight": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", + "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.14.5", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.15.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.15.3.tgz", + "integrity": "sha512-O0L6v/HvqbdJawj0iBEfVQMc3/6WP+AeOsovsIgBFyJaG+W2w7eqvZB7puddATmWuARlm1SX7DwxJ/JJUnDpEA==", + "dev": true + }, + "@babel/template": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.14.5.tgz", + "integrity": "sha512-6Z3Po85sfxRGachLULUhOmvAaOo7xCvqGQtxINai2mEGPFm6pQ4z5QInFnUrRpfoSV60BnjyF5F3c+15fxFV1g==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.14.5", + "@babel/parser": "^7.14.5", + "@babel/types": "^7.14.5" + } + }, + "@babel/traverse": { + "version": "7.15.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.15.0.tgz", + "integrity": "sha512-392d8BN0C9eVxVWd8H6x9WfipgVH5IaIoLp23334Sc1vbKKWINnvwRpb4us0xtPaCumlwbTtIYNA0Dv/32sVFw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.14.5", + "@babel/generator": "^7.15.0", + "@babel/helper-function-name": "^7.14.5", + "@babel/helper-hoist-variables": "^7.14.5", + "@babel/helper-split-export-declaration": "^7.14.5", + "@babel/parser": "^7.15.0", + "@babel/types": "^7.15.0", + "debug": "^4.1.0", + "globals": "^11.1.0" + } + }, + "@babel/types": { + "version": "7.15.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.0.tgz", + "integrity": "sha512-OBvfqnllOIdX4ojTHpwZbpvz4j3EWyjkZEdmjH0/cgsd6QOdSgU8rLSk6ard/pcW7rlmjdVSX/AWOaORR1uNOQ==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.14.9", + "to-fast-properties": "^2.0.0" + } + }, + "@eslint/eslintrc": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", + "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", + "dev": true, + "requires": { + "ajv": "^6.12.4", + "debug": "^4.1.1", + "espree": "^7.3.0", + "globals": "^13.9.0", + "ignore": "^4.0.6", + "import-fresh": "^3.2.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "strip-json-comments": "^3.1.1" + }, + "dependencies": { + "globals": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.10.0.tgz", + "integrity": "sha512-piHC3blgLGFjvOuMmWZX60f+na1lXFDhQXBf1UYp2fXPXqvEUbOhNwi6BsQ0bQishwedgnjkwv1d9zKf+MWw3g==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + } + } + }, + "@humanwhocodes/config-array": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", + "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", + "dev": true, + "requires": { + "@humanwhocodes/object-schema": "^1.2.0", + "debug": "^4.1.1", + "minimatch": "^3.0.4" + } + }, + "@humanwhocodes/object-schema": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.0.tgz", + "integrity": "sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w==", + "dev": true + }, + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true + }, + "acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true + }, + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true + }, + "babel-eslint": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.1.0.tgz", + "integrity": "sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.7.0", + "@babel/traverse": "^7.7.0", + "@babel/types": "^7.7.0", + "eslint-visitor-keys": "^1.0.0", + "resolve": "^1.12.0" + } + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "enquirer": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "dev": true, + "requires": { + "ansi-colors": "^4.1.1" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "eslint": { + "version": "7.32.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", + "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", + "dev": true, + "requires": { + "@babel/code-frame": "7.12.11", + "@eslint/eslintrc": "^0.4.3", + "@humanwhocodes/config-array": "^0.5.0", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "enquirer": "^2.3.5", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^2.1.0", + "eslint-visitor-keys": "^2.0.0", + "espree": "^7.3.1", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.1.2", + "globals": "^13.6.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.0.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "progress": "^2.0.0", + "regexpp": "^3.1.0", + "semver": "^7.2.1", + "strip-ansi": "^6.0.0", + "strip-json-comments": "^3.1.0", + "table": "^6.0.9", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", + "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true + }, + "globals": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.10.0.tgz", + "integrity": "sha512-piHC3blgLGFjvOuMmWZX60f+na1lXFDhQXBf1UYp2fXPXqvEUbOhNwi6BsQ0bQishwedgnjkwv1d9zKf+MWw3g==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "eslint-config-fliplet": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/eslint-config-fliplet/-/eslint-config-fliplet-1.0.7.tgz", + "integrity": "sha512-RijO3npOtsyeHBEFBATG+7VMkvznE/FWCWOu2mRZLDG6r41FZQ/+gUpCHOBiRVQZ+kXOhZ+gNidMB7mOliTQcQ==", + "dev": true + }, + "eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + } + }, + "eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + }, + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + }, + "espree": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", + "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", + "dev": true, + "requires": { + "acorn": "^7.4.0", + "acorn-jsx": "^5.3.1", + "eslint-visitor-keys": "^1.3.0" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" + }, + "dependencies": { + "estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true + } + } + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "requires": { + "estraverse": "^5.2.0" + }, + "dependencies": { + "estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true + } + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "requires": { + "flat-cache": "^3.0.4" + } + }, + "flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "requires": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + } + }, + "flatted": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.2.tgz", + "integrity": "sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA==", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, + "glob": { + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", + "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + }, + "import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "is-core-module": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.5.0.tgz", + "integrity": "sha512-TXCMSDsEHMEEZ6eCA8rwRDbLu55MRGmrctljsBX/2v1d9/GzqHOxW5c5oPSgrUt2vBFXebu9rGqckXGPWOlYpg==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, + "lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", + "dev": true + }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=", + "dev": true + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "requires": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + } + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true + }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true + }, + "require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true + }, + "resolve": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", + "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", + "dev": true, + "requires": { + "is-core-module": "^2.2.0", + "path-parse": "^1.0.6" + } + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + } + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "table": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/table/-/table-6.7.1.tgz", + "integrity": "sha512-ZGum47Yi6KOOFDE8m223td53ath2enHcYLgOCjGr5ngu8bdIARQk6mN/wRMv4yMRcHnCSnHbCEha4sobQx5yWg==", + "dev": true, + "requires": { + "ajv": "^8.0.1", + "lodash.clonedeep": "^4.5.0", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ajv": { + "version": "8.6.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.2.tgz", + "integrity": "sha512-9807RlWAgT564wT+DjeyU5OFMPjmzxVobvDFmNAhY+5zD6A2ly3jDp6sgnfyDtlIQ+7H97oc/DGCzzfu9rjw9w==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + } + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true + }, + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1" + } + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "v8-compile-cache": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", + "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..42524e9 --- /dev/null +++ b/package.json @@ -0,0 +1,7 @@ +{ + "devDependencies": { + "babel-eslint": "^10.1.0", + "eslint": "^7.32.0", + "eslint-config-fliplet": "^1.0.7" + } +} diff --git a/vendor/validator.js b/vendor/validator.js index b881fea..d678d2f 100755 --- a/vendor/validator.js +++ b/vendor/validator.js @@ -41,6 +41,14 @@ getValue($this) && $this.trigger('input.bs.validator') }) }) + this.$element.find('[data-nomatch]').each(function () { + var $this = $(this) + var target = $this.attr('data-nomatch') + + $(target).on('input.bs.validator', function (e) { + getValue($this) && $this.trigger('input.bs.validator') + }) + }) // run validators for fields with values, but don't clobber server-side errors this.$inputs.filter(function () { @@ -64,7 +72,8 @@ custom: {}, errors: { match: 'Does not match', - minlength: 'Not long enough' + minlength: 'Not long enough', + nomatch: 'Can not match' }, feedback: { success: 'glyphicon-ok', @@ -83,11 +92,15 @@ var target = $el.attr('data-match') return $el.val() !== $(target).val() && Validator.DEFAULTS.errors.match }, + 'nomatch': function ($el) { + var target = $el.attr('data-nomatch') + return $el.val() === $(target).val() && Validator.DEFAULTS.errors.nomatch + }, 'minlength': function ($el) { var minlength = $el.attr('data-minlength') return $el.val().length < minlength && Validator.DEFAULTS.errors.minlength } - } + }; Validator.prototype.update = function () { var self = this @@ -391,4 +404,4 @@ }) }) -}(jQuery); +}(jQuery); \ No newline at end of file diff --git a/widget.json b/widget.json index 5449251..37a42b2 100644 --- a/widget.json +++ b/widget.json @@ -12,6 +12,7 @@ "lodash", "fliplet-app-submissions", "fliplet-media", + "fliplet-socket", "fliplet-studio-ui", "handlebars", "bootstrap", @@ -28,7 +29,8 @@ "vendor/bootstrap-select.min.js", "vendor/validator.js", "css/interface.css", - "js/interface.js" + "js/interface.js", + "js/apnValidation.js" ] }, "build": {