Skip to content

Instantly share code, notes, and snippets.

@khaledosman
Last active September 4, 2019 07:35
Show Gist options
  • Save khaledosman/513c31e30566e12bb4fbae16392cfaee to your computer and use it in GitHub Desktop.
Save khaledosman/513c31e30566e12bb4fbae16392cfaee to your computer and use it in GitHub Desktop.
NodeJS wrapper functions around Amazon Dash Replenishment API
const fetch = require('node-fetch')
const { URLSearchParams } = require('url')
const { dateFromJSON, dateToJSON } = require('../database/utils')
const { User } = require('../database')
const DRS_API_BASE_URL = 'https://dash-replenishment-service-na.amazon.com'
const CLIENT_SECRET = process.env.DRS_CLIENT_SECRET
// const REDIRECT_URL = `${process.env.BASE_URL}/api/amazonLWA`
async function handleErrors (response) {
const json = await response.json()
if (!response.ok) {
console.log('ERROR: ' + JSON.stringify(json))
throw Error(json.error_description || json.error || response.statusText)
}
return json
}
async function deregisterUserFromDRSIfHesRegistered ({ _id, amazon_drs_token: amazonDRSToken, os, bundle_id: bundleId }) {
if (amazonDRSToken && amazonDRSToken.access_token) {
const { access_token: accessToken } = await ensureTokenValid({ _id, amazon_drs_token: amazonDRSToken }, { os: os, bundleId: bundleId })
return Promise.all([
deregisterDRS(accessToken),
User.findByIdAndUpdate(_id, { amazon_drs_token: {}, is_subscribed_to_drs: false }).lean()
])
} else {
return Promise.resolve()
}
}
function getOrderInfo (eventInstanceId, token) {
return fetch(`${DRS_API_BASE_URL}/getOrderInfo/${eventInstanceId}`, {
method: 'GET',
headers: {
'x-amzn-accept-type': '[email protected]',
'x-amzn-type-version': '[email protected]',
Authorization: `Bearer ${token}`
}
})
.then(handleErrors)
}
function getSubscriptionInfo (token) {
return fetch(`${DRS_API_BASE_URL}/subscriptionInfo`, {
method: 'GET',
headers: {
'x-amzn-accept-type': '[email protected]',
'x-amzn-type-version': '[email protected]',
Authorization: `Bearer ${token}`
}
})
.then(handleErrors)
}
function cancelTestOrder (slotId, token) {
return fetch(`${DRS_API_BASE_URL}/testOrders/slots/${slotId}`, {
method: 'DELETE',
headers: {
'x-amzn-accept-type': '[email protected]',
'x-amzn-type-version': '[email protected]',
Authorization: `Bearer ${token}`
}
})
.then(handleErrors)
}
function cancelAllTestOrders (token) {
return fetch(`${DRS_API_BASE_URL}/testOrders`, {
method: 'DELETE',
headers: {
'x-amzn-accept-type': '[email protected]',
'x-amzn-type-version': '[email protected]',
Authorization: `Bearer ${token}`
}
})
.then(handleErrors)
}
function deregisterDRS (token) {
return fetch(`${DRS_API_BASE_URL}/registration`, {
method: 'DELETE',
headers: {
'x-amzn-accept-type': '[email protected]',
'x-amzn-type-version': '[email protected]',
Authorization: `Bearer ${token}`
}
})
.then(handleErrors)
}
function sendSlotStatus (slotId, token, body) {
/*
{
"expectedReplenishmentDate" : "2015-12-28T10:00:00Z",
"remainingQuantityInUnit" : 3.5,
"originalQuantityInUnit" : 10,
"totalQuantityOnHand" : 20,
"lastUseDate" : "2015-12-21T10:00:00Z"
}
*/
return fetch(`${DRS_API_BASE_URL}/slotStatus/${slotId}`, {
method: 'POST',
headers: {
'x-amzn-accept-type': '[email protected]',
'x-amzn-type-version': '[email protected]',
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`
},
body: JSON.stringify(body)
})
.then(async function handleErrors (response) {
const text = await response.text()
if (!response.ok) {
console.log('ERROR: ' + text)
throw Error(text || response.statusText)
}
return text
})
}
function sendDeviceStatus (token, mostRecentlyActiveDate) {
return fetch(`${DRS_API_BASE_URL}/deviceStatus`, {
method: 'POST',
headers: {
'x-amzn-accept-type': '[email protected]',
'x-amzn-type-version': '[email protected]',
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`
},
body: JSON.stringify({
mostRecentlyActiveDate
})
})
.then(handleErrors)
}
function replenish (slotId, token) {
return fetch(`${DRS_API_BASE_URL}/replenish/${slotId}`, {
method: 'POST',
headers: {
'x-amzn-accept-type': '[email protected]',
'x-amzn-type-version': '[email protected]',
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`
}
})
.then(handleErrors)
}
function createToken (code, os, bundleId) {
const REDIRECT_URL = `${process.env.BASE_URL}/api/amazonLWA`
const CLIENT_ID = os === 'iOS' ? process.env.DRS_CLIENT_ID_IOS : process.env.DRS_CLIENT_ID
// const REDIRECT_URL = `amzn://${bundleId}`
const params = new URLSearchParams()
params.append('grant_type', 'authorization_code')
params.append('client_id', CLIENT_ID)
params.append('client_secret', CLIENT_SECRET)
params.append('redirect_uri', REDIRECT_URL)
params.append('code', code)
return fetch('https://api.amazon.com/auth/o2/token', {
method: 'POST',
body: params,
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
})
.then(handleErrors)
}
function createTokenFromSDK ({ redirectURL, code, codeVerifier, os, bundleId }) {
const CLIENT_ID = os === 'iOS' ? process.env.DRS_CLIENT_ID_IOS : process.env.DRS_CLIENT_ID
// const REDIRECT_URL = `amzn://${bundleId}`
const params = new URLSearchParams()
params.append('grant_type', 'authorization_code')
params.append('client_id', CLIENT_ID)
params.append('redirect_uri', redirectURL)
params.append('code', code)
params.append('code_verifier', codeVerifier)
console.log({ CLIENT_ID, redirectURL, code, codeVerifier })
return fetch('https://api.amazon.com/auth/o2/token', {
method: 'POST',
body: params,
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
})
.then(handleErrors)
}
// authorization code, client ID, and redirect UR, code_verifier
/*
The access token is valid for one hour. When the access token expires, or is about to expire, you can exchange the refresh token for a new access token.
*/
function refreshAccessToken (refreshToken, { os }) {
const CLIENT_ID = os === 'iOS' ? process.env.DRS_CLIENT_ID_IOS : process.env.DRS_CLIENT_ID
// const REDIRECT_URL = `amzn://${bundleId}`
const params = new URLSearchParams()
params.append('grant_type', 'refresh_token')
params.append('client_id', CLIENT_ID)
params.append('client_secret', CLIENT_SECRET)
// params.append('redirect_uri', REDIRECT_URL)
params.append('refresh_token', refreshToken)
return fetch('https://api.amazon.com/auth/o2/token', {
method: 'POST',
body: params,
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
})
.then(handleErrors)
}
async function ensureTokenValid ({ _id, amazon_drs_token: amazonDRSToken }, { os, bundleId }) {
const { access_token: accessToken, expires_in: expiresIn, refresh_token: refreshToken, created_at: createdAt } = amazonDRSToken
if (accessToken && refreshToken && expiresIn && createdAt) {
const creationDate = dateFromJSON(createdAt)
if (Date.now() >= creationDate.getTime() + (expiresIn * 1000)) {
// refresh token
const newTokenInfo = await refreshAccessToken(refreshToken, { os, bundleId })
await User.findByIdAndUpdate(_id, { amazon_drs_token: { ...newTokenInfo, created_at: dateToJSON(new Date()) } })
.select({ _id: 1 })
.lean()
return newTokenInfo
} else {
// token still valid
return { access_token: accessToken, expires_in: expiresIn, refresh_token: refreshToken, created_at: createdAt }
}
} else {
// user not logged in wih DRS, or DRS not enabled
throw Error('no token found')
}
}
module.exports = {
refreshAccessToken,
createToken,
createTokenFromSDK,
replenish,
sendDeviceStatus,
sendSlotStatus,
deregisterDRS,
cancelAllTestOrders,
cancelTestOrder,
getOrderInfo,
getSubscriptionInfo,
ensureTokenValid,
deregisterUserFromDRSIfHesRegistered
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment