apple-music-token-node
Advanced tools
Comparing version 1.1.1 to 2.0.0
95
index.js
@@ -7,26 +7,4 @@ const jwt = require('jsonwebtoken'); | ||
/** | ||
* @function getToken - Generates a developer token for use with the Apple Music API | ||
* @param {string} certPath - Absolute path to your .p8 private key file | ||
* @param {string} teamId - Developer team id | ||
* @param {string} keyId - Music Key id | ||
* @param {string} hoursValid - Hours until the key should expire | ||
* @returns {{token: string, expiresAt: timestamp, expiresIn: seconds}} - | ||
* ES256 encoded JWT token, time of expiration (in seconds), and also the seconds | ||
* remaining until expiration occurs. | ||
* | ||
* (`expiresIn` is sort of a lazy solution on my part, but is included to avoid | ||
* requiring anyone to deal with side-effects from date conversions between server | ||
* and client code.) | ||
*/ | ||
const getToken = (certPath, teamId, keyId, hoursValid = 12) => { | ||
if (!fs.lstatSync(certPath).isFile()) { | ||
throw new InvalidCertPathError( | ||
{ certPath }, | ||
'Cert path provided to apple-music-token-node is not a valid file path', | ||
); | ||
} | ||
const getResult = (cert, teamId, keyId, hoursValid) => { | ||
const alg = 'ES256'; | ||
const cert = fs.readFileSync(certPath); | ||
const now = moment().unix(); | ||
@@ -60,2 +38,71 @@ const expiration = moment().add(hoursValid, 'hours').unix(); | ||
module.exports = getToken; | ||
/** | ||
* @function getToken - Generates a developer token for use with the Apple Music API | ||
* @param {string} certPath - Absolute path to your .p8 private key file | ||
* @param {string} teamId - Developer team id | ||
* @param {string} keyId - Music Key id | ||
* @param {string} hoursValid - Hours until the key should expire | ||
* @returns {{token: string, expiresAt: timestamp, expiresIn: seconds}} - | ||
* ES256 encoded JWT token, time of expiration (in seconds), and also the seconds | ||
* remaining until expiration occurs. | ||
* | ||
* (`expiresIn` is sort of a lazy solution on my part, but is included to avoid | ||
* requiring anyone to deal with side-effects from date conversions between server | ||
* and client code.) | ||
*/ | ||
const getToken = (certPath, teamId, keyId, hoursValid = 12) => { | ||
try { | ||
if (!fs.lstatSync(certPath).isFile()) { | ||
throw new InvalidCertPathError( | ||
{ certPath }, | ||
'Cert path provided to apple-music-token-node is not a valid file path', | ||
); | ||
} | ||
} catch (err) { | ||
throw err; | ||
} | ||
const cert = fs.readFileSync(certPath); | ||
return getResult(cert, teamId, keyId, hoursValid); | ||
}; | ||
/** | ||
* @function getTokenAsync - (Async) Generates a developer token for use with the Apple Music API | ||
* @param {string} certPath - Absolute path to your .p8 private key file | ||
* @param {string} teamId - Developer team id | ||
* @param {string} keyId - Music Key id | ||
* @param {string} hoursValid - Hours until the key should expire | ||
* @returns {Promise} - | ||
* Resolves to `{token: string, expiresAt: timestamp, expiresIn: seconds}` (ES256 encoded JWT token, | ||
* time of expiration (in seconds), and also the seconds remaining until expiration occurs). | ||
*/ | ||
const getTokenAsync = (certPath, teamId, keyId, hoursValid = 12) => { | ||
return new Promise((resolve, reject) => { | ||
fs.lstat(certPath, (err, stats) => { | ||
if (err) { | ||
return reject(err); | ||
} | ||
if (!stats.isFile()) { | ||
return reject(new InvalidCertPathError( | ||
{ certPath }, | ||
'Cert path provided to apple-music-token-node is not a valid file path', | ||
)); | ||
} | ||
fs.readFile(certPath, (err, cert) => { | ||
if (err) { | ||
return reject(err); | ||
} | ||
const result = getResult(cert, teamId, keyId, hoursValid); | ||
return resolve(result); | ||
}); | ||
}); | ||
}); | ||
}; | ||
module.exports.getToken = getToken; | ||
module.exports.getTokenAsync = getTokenAsync; |
const amtn = require('../index'); | ||
const { getToken } = amtn; | ||
module.exports = (filePath, teamId, keyId, hoursValid) => { | ||
try { | ||
return amtn(filePath.trim(), teamId.trim(), keyId.trim(), hoursValid); | ||
return getToken(filePath.trim(), teamId.trim(), keyId.trim(), hoursValid); | ||
} catch (error) { | ||
@@ -7,0 +9,0 @@ console.log(error); |
{ | ||
"name": "apple-music-token-node", | ||
"version": "1.1.1", | ||
"version": "2.0.0", | ||
"description": "generate developer tokens for the apple music api in node, with a CLI mode for development.", | ||
@@ -31,13 +31,13 @@ "main": "index.js", | ||
"dependencies": { | ||
"jsonwebtoken": "^8.2.0", | ||
"moment": "^2.21.0" | ||
"jsonwebtoken": "^8.5.1", | ||
"moment": "^2.24.0" | ||
}, | ||
"devDependencies": { | ||
"chai": "^4.1.2", | ||
"chalk": "^2.4.1", | ||
"chai": "^4.2.0", | ||
"chalk": "^2.4.2", | ||
"clear": "^0.1.0", | ||
"figlet": "^1.2.0", | ||
"inquirer": "^6.0.0", | ||
"mocha": "^5.2.0" | ||
"figlet": "^1.2.3", | ||
"inquirer": "^6.5.0", | ||
"mocha": "^6.2.0" | ||
} | ||
} |
@@ -1,3 +0,5 @@ | ||
## apple music token generator for node | ||
## apple music token node | ||
[![Build Status](https://travis-ci.org/sheminusminus/apple-music-token-node.svg?branch=master)](https://travis-ci.org/sheminusminus/apple-music-token-node) | ||
use javascript to generate your developer tokens for use with the apple music api. | ||
@@ -9,2 +11,4 @@ | ||
npm | ||
``` | ||
@@ -14,2 +18,7 @@ npm install -S apple-music-token-node | ||
yarn | ||
``` | ||
yarn add apple-music-token-node | ||
``` | ||
-or- | ||
@@ -19,2 +28,28 @@ | ||
### updating from v1.x to v2.x | ||
if you don't want to use the async api available in v2, the only code change you need to make is to update from this: | ||
``` | ||
const getToken = require('apple-music-token-node'); | ||
``` | ||
to this: | ||
``` | ||
const { getToken } = require('apple-music-token-node'); | ||
``` | ||
and you're done! | ||
if you *would* like to use the new async api, you can require it like so: | ||
``` | ||
const { getTokenAsync } = require('apple-music-token-node'); | ||
``` | ||
`getTokenAsync` takes the same arguments as `getToken`, and returns a promise (and thus is usable with `async`/`await`). the promise resolves to the same object that would be returned by using `getToken`. | ||
### usage | ||
@@ -24,6 +59,7 @@ | ||
synchronous usage: | ||
``` | ||
const path = require('path'); | ||
const getToken = require('apple-music-token-node'); | ||
const { getToken } = require('apple-music-token-node'); | ||
@@ -38,2 +74,29 @@ const { teamId, keyId } = require('./path-to-your-config'); | ||
asynchronous usage: | ||
``` | ||
const path = require('path'); | ||
const { getTokenAsync } = require('apple-music-token-node'); | ||
const { teamId, keyId } = require('./path-to-your-config'); | ||
const certPath = path.resolve(__dirname, './path-to-your-p8-file'); | ||
const generate = async () => { | ||
const tokenData = await getTokenAsync(certPath, teamId, keyId); | ||
// tokenData == { token: 'generated_token', expiresAt: timeInSeconds } | ||
// ... | ||
}; | ||
// -or- | ||
const generate = () => { | ||
getTokenAsync(certPath, teamId, keyId).then((tokenData) => { | ||
// tokenData == { token: 'generated_token', expiresAt: timeInSeconds } | ||
// ... | ||
}); | ||
}; | ||
``` | ||
### cli mode | ||
@@ -43,2 +106,4 @@ | ||
npm | ||
``` | ||
@@ -48,5 +113,11 @@ npm i -g apple-music-token-node | ||
then you can run | ||
yarn | ||
``` | ||
yarn add -g apple-music-token-node | ||
``` | ||
then you can run: | ||
``` | ||
amtn | ||
@@ -58,3 +129,3 @@ ``` | ||
![Data entered correctly:](https://i.imgur.com/Z9yXFte.png) | ||
![Basic file path suggestion for incorrect entries:](https://i.imgur.com/nUunmcI.png) | ||
![Data entered correctly](https://i.imgur.com/Z9yXFte.png) | ||
![Basic file path suggestion for incorrect entries](https://i.imgur.com/nUunmcI.png) |
@@ -6,38 +6,83 @@ const chai = require('chai'); | ||
const amtn = require('../index'); | ||
const InvalidCertPathError = require('../invalidCertPathError'); | ||
const config = require('./testData/config'); | ||
const { assert, expect } = chai; | ||
const { assert } = chai; | ||
const { getToken, getTokenAsync } = amtn; | ||
const mockCertFile = './testData/APNsAuthKey_6F44JJ9SDF_com.example.FakeApp_UB40ZXKCDZ.p8'; | ||
describe('apple music token node', () => { | ||
it('should exist', () => { | ||
assert.isFunction(amtn); | ||
describe('sync api', () => { | ||
it('should exist', () => { | ||
assert.isFunction(getToken); | ||
}); | ||
it('should return an object `{ token, expiresAt }` for valid params', () => { | ||
const validPath = path.resolve(__dirname, mockCertFile); | ||
const tokenData = getToken(validPath, config.testTeamId, config.testKeyId, 1); | ||
assert.isObject(tokenData); | ||
assert.isString(tokenData.token); | ||
assert.isNumber(tokenData.expiresAt); | ||
}); | ||
it('if no `hoursValid` provided, should complete using 12h default', () => { | ||
const validPath = path.resolve(__dirname, mockCertFile); | ||
const { expiresAt } = getToken(validPath, config.testTeamId, config.testKeyId); | ||
const soon = moment().add(13, 'hours'); | ||
const sooner = moment().add(11, 'hours'); | ||
assert.isTrue(moment.unix(expiresAt).isSameOrBefore(soon, 'hour')); | ||
assert.isTrue(moment.unix(expiresAt).isSameOrAfter(sooner, 'hour')); | ||
}); | ||
it('should throw an error if the certificate file does not exist', () => { | ||
const invalidPath = path.resolve(__dirname, 'sex_panther.p8'); | ||
function throwing() { | ||
getToken(invalidPath, config.testTeamId, config.testKeyId, 1); | ||
} | ||
assert.throws(throwing); | ||
}); | ||
}); | ||
it('should return an object `{ token, expiresAt }` for valid params', () => { | ||
const validPath = path.resolve(__dirname, mockCertFile); | ||
const tokenData = amtn(validPath, config.testTeamId, config.testKeyId, 1); | ||
assert.isObject(tokenData); | ||
assert.isString(tokenData.token); | ||
assert.isNumber(tokenData.expiresAt); | ||
}); | ||
describe('async api', () => { | ||
it('should exist', () => { | ||
assert.isFunction(getTokenAsync); | ||
}); | ||
it('if no `hoursValid` provided, should complete using 12h default', () => { | ||
const validPath = path.resolve(__dirname, mockCertFile); | ||
const { expiresAt } = amtn(validPath, config.testTeamId, config.testKeyId); | ||
const soon = moment().add(13, 'hours'); | ||
const sooner = moment().add(11, 'hours'); | ||
assert.isTrue(moment.unix(expiresAt).isSameOrBefore(soon, 'hour')); | ||
assert.isTrue(moment.unix(expiresAt).isSameOrAfter(sooner, 'hour')); | ||
}); | ||
it('should return a promise', () => { | ||
const validPath = path.resolve(__dirname, mockCertFile); | ||
const tokenDataPromise = getTokenAsync(validPath, config.testTeamId, config.testKeyId, 1); | ||
assert.instanceOf(tokenDataPromise, Promise, 'result is not a promise'); | ||
}); | ||
it('should throw an error if the certificate file does not exist', () => { | ||
const invalidPath = path.resolve(__dirname, 'sex_panther.p8'); | ||
function throwing() { | ||
amtn(invalidPath, config.testTeamId, config.testKeyId, 1); | ||
} | ||
assert.throws(throwing); | ||
it('should resolve to an object `{ token, expiresAt }` for valid params', async () => { | ||
const validPath = path.resolve(__dirname, mockCertFile); | ||
const tokenData = await getTokenAsync(validPath, config.testTeamId, config.testKeyId, 1); | ||
assert.isObject(tokenData); | ||
assert.isString(tokenData.token); | ||
assert.isNumber(tokenData.expiresAt); | ||
}); | ||
it('if no `hoursValid` provided, should complete using 12h default', async () => { | ||
const validPath = path.resolve(__dirname, mockCertFile); | ||
const { expiresAt } = await getTokenAsync(validPath, config.testTeamId, config.testKeyId); | ||
const soon = moment().add(13, 'hours'); | ||
const sooner = moment().add(11, 'hours'); | ||
assert.isTrue(moment.unix(expiresAt).isSameOrBefore(soon, 'hour')); | ||
assert.isTrue(moment.unix(expiresAt).isSameOrAfter(sooner, 'hour')); | ||
}); | ||
it('should reject/throw if the certificate file does not exist', async () => { | ||
let err = null; | ||
let result = null; | ||
const invalidPath = path.resolve(__dirname, 'sex_panther.p8'); | ||
try { | ||
result = await getTokenAsync(invalidPath, config.testTeamId, config.testKeyId, 1); | ||
} catch (e) { | ||
err = e; | ||
} | ||
assert.isNotNull(err); | ||
assert.isNull(result); | ||
}); | ||
}); | ||
}); |
324
124
15410
13
Updatedjsonwebtoken@^8.5.1
Updatedmoment@^2.24.0