Skip to content

Commit

Permalink
Merge pull request #1 from erikdesjardins/impl
Browse files Browse the repository at this point in the history
Impl
  • Loading branch information
erikdesjardins committed Feb 14, 2017
2 parents 7b22b72 + 3e05fc0 commit 3dc256a
Show file tree
Hide file tree
Showing 5 changed files with 595 additions and 8 deletions.
4 changes: 4 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ node_js:
- 7
- 6
- 4
branches:
only:
- master
- /^v[0-9]/
script:
- nyc npm test
after_success:
Expand Down
16 changes: 13 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,28 @@ Deploy Edge extensions to the Windows Store.

## Usage

Note: `edge-extension-deploy` requires `Promise` support.
If your environment does not natively support promises, you'll need to provide [your own polyfill](https://github.com/floatdrop/pinkie).

```js
var fs = require('fs');
var deploy = require('edge-extension-deploy');

deploy({
// Azure AD credentials
tenantId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
clientId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
clientSecret: 'bXlDbGllbnRTZWNyZXQ=',

// Windows Store ID of the extension (from the Dev Center dashboard)
appId: '123456789ABC',

// OPTIONAL: if specified, will push a flight submission instead of the main submission
flightId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',

// ReadStream of an (unsigned) appx
appx: fs.createReadStream('path/to/extension.appx')
}).then(function() {
// success!
}, function(err) {
// failure :(
// errors are sanitized, so your tokens will not be leaked
});
```
161 changes: 161 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,167 @@
'use strict';

var request = require('superagent');
var yazl = require('yazl');

var REQUIRED_FIELDS = ['tenantId', 'clientId', 'clientSecret', 'appId', 'appx'];

function sleep(ms) {
return new Promise(function(resolve) {
setTimeout(resolve, ms);
});
}

module.exports = function deploy(options) {
var tenantId = options.tenantId;
var clientId = options.clientId;
var clientSecret = options.clientSecret;
var appId = options.appId;
var flightId = options.flightId;
var appx = options.appx;

var appAndFlight = 'https://manage.devcenter.microsoft.com/v1.0/my/applications/' + appId + (flightId ? '/flights/' + flightId : '');
var accessToken, submissionInfo;

// https://docs.microsoft.com/en-us/windows/uwp/monetize/python-code-examples-for-the-windows-store-submission-api
return Promise.resolve()
// options validation
.then(function() {
REQUIRED_FIELDS.forEach(function(field) {
if (!options[field]) {
throw new Error('Missing required field: ' + field);
}
});
})
// fetch Azure AD access token
.then(function() {
// https://docs.microsoft.com/en-us/windows/uwp/monetize/create-and-manage-submissions-using-windows-store-services#obtain-an-azure-ad-access-token
return request
.post('https://login.microsoftonline.com/' + tenantId + '/oauth2/token')
.field('grant_type', 'client_credentials')
.field('resource', 'https://manage.devcenter.microsoft.com')
.field('client_id', clientId)
.field('client_secret', clientSecret)
.then(function(response) {
var token = response.body.access_token;
if (!token) {
throw new Error('No access token received.');
}
accessToken = token;
}, function(err) {
throw new Error('Failed to fetch access token: ' + (err.response.body.error || err.response.status));
});
})
// get app or flight info
.then(function() {
// https://docs.microsoft.com/en-us/windows/uwp/monetize/get-an-app
// https://docs.microsoft.com/en-us/windows/uwp/monetize/get-a-flight
return request
.get(appAndFlight)
.set('Authorization', 'Bearer ' + accessToken)
.then(function(response) {
if (flightId) {
return response.body.pendingFlightSubmission;
} else {
return response.body.pendingApplicationSubmission;
}
}, function(err) {
throw new Error('Failed to fetch ' + (flightId ? 'flight' : 'app') + ': ' + (err.response.body.code || err.response.status));
});
})
// delete existing submission (if present)
.then(function(pendingSubmission) {
if (!pendingSubmission) return;

// https://docs.microsoft.com/en-us/windows/uwp/monetize/delete-an-app-submission
// https://docs.microsoft.com/en-us/windows/uwp/monetize/delete-a-flight-submission
return request
.delete(appAndFlight + '/submissions/' + pendingSubmission.id)
.set('Authorization', 'Bearer ' + accessToken)
.then(function() {
// success
}, function(err) {
throw new Error('Failed to delete previous submission: ' + (err.response.body.code || err.response.status));
});
})
// create new submission
.then(function() {
// https://docs.microsoft.com/en-us/windows/uwp/monetize/create-an-app-submission
// https://docs.microsoft.com/en-us/windows/uwp/monetize/create-an-app-submission
return request
.post(appAndFlight + '/submissions')
.set('Authorization', 'Bearer ' + accessToken)
.then(function(response) {
submissionInfo = response.body;
}, function(err) {
throw new Error('Failed to create new submission: ' + (err.response.body.code || err.response.status));
});
})
// prepare zip file
.then(function() {
return new Promise(function(resolve, reject) {
var zipFile = new yazl.ZipFile();
zipFile.addReadStream(appx, 'package.appx');
zipFile.end();

var bufs = [];
zipFile.outputStream.on('data', function(buf) {
bufs.push(buf);
});
zipFile.outputStream.on('end', function() {
resolve(Buffer.concat(bufs));
});
zipFile.outputStream.on('error', reject);
});
})
// upload package
.then(function(zipFileBuffer) {
return request
.put(submissionInfo.fileUploadUrl.replace(/\+/g, '%2B'))
.set('x-ms-blob-type', 'BlockBlob')
.send(zipFileBuffer)
.then(function() {
// success
}, function(err) {
throw new Error('Failed to upload package: ' + (err.response.body.code || err.response.status));
});
})
// commit new submission
.then(function() {
// https://docs.microsoft.com/en-us/windows/uwp/monetize/commit-an-app-submission
// https://docs.microsoft.com/en-us/windows/uwp/monetize/commit-an-app-submission
return request
.post(appAndFlight + '/submissions/' + submissionInfo.id + '/commit')
.set('Authorization', 'Bearer ' + accessToken)
.then(function() {
// success
}, function(err) {
throw new Error('Failed to commit submission: ' + (err.response.body.code || err.response.status));
});
})
// poll for completion
.then(function poll() {
// https://docs.microsoft.com/en-us/windows/uwp/monetize/get-status-for-an-app-submission
// https://docs.microsoft.com/en-us/windows/uwp/monetize/get-status-for-a-flight-submission
return request
.get(appAndFlight + '/submissions/' + submissionInfo.id + '/status')
.set('Authorization', 'Bearer ' + accessToken)
.then(function(response) {
// https://github.com/Microsoft/StoreBroker/blob/master/Documentation/USAGE.md#status-progression
var status = response.body.status;
if (status === 'CommitStarted') {
// try again
return sleep(30000).then(poll);
} else if (
status !== 'PreProcessing' &&
// anything other than PreProcessing is unlikely,
// but techincally possible if the store is _really_ fast
status !== 'Certification' &&
status !== 'Release'
) {
throw new Error('Failed: ' + status + ' ' + JSON.stringify(response.body.statusDetails));
}
}, function(err) {
throw new Error('Failed to poll for commit status: ' + (err.response.body.code || err.response.status));
});
});
};
11 changes: 6 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "edge-extension-deploy",
"version": "0.0.1",
"version": "0.1.0",
"description": "Deploy Edge extensions to the Windows Store.",
"main": "index.js",
"scripts": {
Expand All @@ -17,12 +17,13 @@
},
"homepage": "https://github.com/erikdesjardins/edge-extension-deploy#readme",
"devDependencies": {
"ava": "^0.13.0",
"ava": "^0.18.1",
"coveralls": "^2.11.8",
"nyc": "^6.0.0",
"superagent-mock": "^1.10.0"
"nyc": "^10.1.2",
"superagent-mock": "^3.2.0"
},
"dependencies": {
"superagent": "^1.8.0"
"superagent": "^3.4.1",
"yazl": "^2.4.2"
}
}
Loading

0 comments on commit 3dc256a

Please sign in to comment.