Skip to content

Commit

Permalink
Merge pull request #166 from renderforest/render-status
Browse files Browse the repository at this point in the history
render-status
  • Loading branch information
narekhovhannisyan authored Mar 26, 2019
2 parents 1b0965d + 781f475 commit d56f2e1
Show file tree
Hide file tree
Showing 13 changed files with 440 additions and 67 deletions.
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,11 @@ Renderforest SDK for Node.js.

[![Build Status](https://travis-ci.org/renderforest/renderforest-sdk-node.svg?branch=master)](https://travis-ci.org/renderforest/renderforest-sdk-node)


### [API Reference](https://developers.renderforest.com)

# Introduction

Welcome to the Renderforest API!
Welcome to the Renderforest API! With our SDK you can use following resources:

* [Projects](https://github.com/renderforest/renderforest-sdk-node/blob/master/docs/PROJECTS_API.md)
* [Project-data](https://github.com/renderforest/renderforest-sdk-node/blob/master/docs/project-data-api/PROJECT_DATA_API.md)
Expand Down
28 changes: 28 additions & 0 deletions docs/PROJECTS_API.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
- [Apply Template Preset on the Project](#apply-template-preset-on-the-project)
- [Duplicate the Project](#duplicate-the-project)
- [Render the Project](#render-the-project)
- [Get rendering status](#get-rendering-status)

### Get All Projects

Expand Down Expand Up @@ -195,4 +196,31 @@ Renderforest.renderProject(4120385, { quality: 360, watermark: 'https://example.

[See example](https://github.com/renderforest/renderforest-sdk-node/blob/master/samples/projects/render-project.js)


### Get rendering status

Our rendering status method uses user's defined `callback` to manage rendering status percentage
and uses error first callback pattern. If you want to unsubscribe from getting rendering status
(before rendering finishes) then simply call
`unsubscribe` (`getRenderingStatus` method returns function to unsubscribe from getting rendering status).

```js
const RenderforestClient = require('@renderforest/sdk-node')

const Renderforest = new RenderforestClient({ signKey: '<signKey>', clientId: -1 })

Renderforest.renderProject(15431416, { quality: 720 })
.then(() => {
const unsubscribe = Renderforest.getRenderingStatus((error, percentage) => {
if (error) {
console.log(error)
}
// take percentage from here
console.log(percentage)
// if you want in unsubscribe (before rendering finishes) for some reason, then simply call unsubscribe
unsubscribe()
})
})
```

**[⬆ back to the top](#projects-api)**
2 changes: 1 addition & 1 deletion docs/project-data-api/PROJECT_DATA_API.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ your changes with API. Save method can be called with chaining.
Renderforest.getProjectData(15220886)
.then((projectDataInstance) =>
projectDataInstance.setMuteMusic(true)
/// do some changes, then call save() method
// do some changes, then call save() method
.save()
)
.catch(console.error)
Expand Down
48 changes: 41 additions & 7 deletions lib/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,21 @@
*/

const QueryString = require('querystring')

const RequestPromise = require('request-promise')
const socketClient = require('socket.io-client')

const { createAuthorizationQuery, setAuthorization } = require('./auth')

const Auth = require('./auth')
const {
API_HOST,
WEB_HOST,
HTTP_DEFAULT_OPTIONS,
SOCKET_HOST,
REND_FINISHED_STATE,
REND_NOT_FINISHED_STATE
} = require('./config')

const { API_HOST, WEB_HOST, HTTP_DEFAULT_OPTIONS } = require('./config')
const { SocketConnectionError } = require('./util/error')

class Api {
/**
Expand Down Expand Up @@ -80,7 +89,6 @@ class Api {
*/
unauthorizedRequest (options) {
const _options = Object.assign({}, HTTP_DEFAULT_OPTIONS, options)

Api.prepareRequest(_options)

return Api.request(_options)
Expand All @@ -93,9 +101,8 @@ class Api {
*/
authorizedRequest (options) {
const _options = Object.assign({}, HTTP_DEFAULT_OPTIONS, options)

Api.prepareRequest(_options)
Auth.setAuthorization(_options, this.signKey, this.clientId)
setAuthorization(_options, this.signKey, this.clientId)

return Api.request(_options)
}
Expand All @@ -107,11 +114,38 @@ class Api {
*/
webRequest (options) {
const _options = Object.assign({}, HTTP_DEFAULT_OPTIONS, options)

Api.appendURI(_options, WEB_HOST)

return RequestPromise(_options)
}

/**
* @param {Function} callback - The callback function for handling render status result.
* @return {unsubscribe}
*/
socketConnection (callback) {
const renderStatus = socketClient(SOCKET_HOST, {
query: createAuthorizationQuery(this.clientId, this.signKey)
})

renderStatus.on('error', (error) => callback(new SocketConnectionError(error)))
renderStatus.on('event', (status) => {
if (status.state === 'rended') {
renderStatus.close()
return callback(null, REND_FINISHED_STATE)
} else {
// if `status.state` is not `rended` but percent is 100, then show rend not finished state
return callback(null, Math.min(REND_NOT_FINISHED_STATE, status.percent))
}
})

// if socket is still open, then force close manually.
const unsubscribe = () => {
renderStatus.close()
}

return unsubscribe
}
}

module.exports = new Api()
29 changes: 25 additions & 4 deletions lib/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,10 @@ const url = require('url')

const AuthUtil = require('./util/auth')

const { SOCKET_ORIGINAL_URL } = require('./config')

/**
* Set Authorization.
* @param {Object} options
* @param {string} signKey
* @param {number} clientId
* Set Authorization for api requests.
*/
const setAuthorization = (options, signKey, clientId) => {
const { path, query } = url.parse(options.uri)
Expand All @@ -35,6 +34,28 @@ const setAuthorization = (options, signKey, clientId) => {
options.headers = Object.assign({}, options.headers, headers)
}

/**
* Builds authorization string for socket connection.
* @return {String}
*/
const createSocketAuthorizationString = (clientId, signKey, timestamp) => AuthUtil.createSocketAuthHash({
clientId,
timestamp,
originalUrl: SOCKET_ORIGINAL_URL
}, signKey)

/**
* Creates authorization query based on auth, clientId and timestamp.
* @return {String}
*/
const createAuthorizationQuery = (clientId, signKey) => {
const timestamp = Date.now()
const auth = createSocketAuthorizationString(clientId, signKey, timestamp)

return `authorization=${auth}&user_id=${clientId}&timestamp=${timestamp}`
}

module.exports = {
createAuthorizationQuery,
setAuthorization
}
8 changes: 8 additions & 0 deletions lib/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,14 @@ class RenderforestClient {
return Projects.renderProject(projectId, params)
}

/**
* @param {Function} callback
* @return {WebSocket}
*/
getRenderingStatus (callback) {
return Projects.getRenderingStatus(callback)
}

/**
* @param {Object} params
* @returns {Promise.<Object>}
Expand Down
4 changes: 4 additions & 0 deletions lib/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ const CONFIG = {
}
},
PROJECT_DATA_API_PREFIX: '/api/v5',
REND_FINISHED_STATE: 100,
REND_NOT_FINISHED_STATE: 99,
SOCKET_HOST: 'wss://sockets.renderforest.com',
SOCKET_ORIGINAL_URL: 'sockets.renderforest.com',
WEB_HOST: 'https://www.renderforest.com',
WEB_PREFIX: '/api/v1'
}
Expand Down
8 changes: 7 additions & 1 deletion lib/resources/projects.js
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,11 @@ const renderProject = (projectId, params) => {
return Api.authorizedRequest(options)
}

/**
* @param {Function} callback - The callback function for handling render status result.
*/
const getRenderingStatus = (callback) => Api.socketConnection(callback)

module.exports = {
getProject,
addProject,
Expand All @@ -163,5 +168,6 @@ module.exports = {
deleteProjectVideos,
applyTemplatePresetOnProject,
duplicateProject,
renderProject
renderProject,
getRenderingStatus
}
107 changes: 59 additions & 48 deletions lib/util/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,53 +8,64 @@

const crypto = require('crypto')

class AuthUtil {
/**
* Creates keyed-hash message authentication code (HMAC).
* Used core `crypto` module cryptographic hash function.
* Secret key - sha512.
* @param {string} text
* @param {string} key
* @returns {string}
*/
static createHMAC (text, key) {
return crypto.createHmac('sha512', key).update(text).digest('hex')
}

/**
* Generates HMAC based on source and key.
* Source is defined as combination of clientId, path, qs, body, nonce and timestamp respectively.
* @param {{clientId, qs, path, body, nonce, timestamp}} options
* @param {string} key
* @returns {string}
*/
static generateHash (options, key) {
const clientId = options.clientId
const qs = options.qs
const path = options.path
const body = options.body
const nonce = options.nonce
const timestamp = options.timestamp
const hashSource = `${clientId}${path}${qs}${body}${nonce}${timestamp}`

return AuthUtil.createHMAC(hashSource, key)
}

/**
* Generates nonce.
* Creates timestamp
* Gets the last 6 chars of the timestamp
* Generates random number between 10-99
* Combined the last two ones.
* @returns {string}
*/
static generateNonce () {
const timestamp = Date.now().toString()
const str = timestamp.substring(timestamp.length - 6)
const suffix = Math.floor(Math.random() * 90 + 9)

return `${str}${suffix}`
}
/**
* Creates keyed-hash message authentication code (HMAC).
* Used core `crypto` module cryptographic hash function.
* Secret key - sha512.
* @param {string} text
* @param {string} key
* @returns {string}
*/
const createHMAC = (text, key) => {
return crypto.createHmac('sha512', key).update(text).digest('hex')
}

/**
* Generates HMAC based on source and key.
* Source is defined as combination of clientId, path, qs, body, nonce and timestamp respectively.
* @param {{clientId, qs, path, body, nonce, timestamp}} options
* @param {string} key
* @returns {string}
*/
const generateHash = (options, key) => {
const { clientId, qs, path, body, nonce, timestamp } = options
const hashSource = `${clientId}${path}${qs}${body}${nonce}${timestamp}`

return createHMAC(hashSource, key)
}

/**
* Generates nonce.
* Creates timestamp
* Gets the last 6 chars of the timestamp
* Generates random number between 10-99
* Combined the last two ones.
* @returns {string}
*/
const generateNonce = () => {
const timestamp = Date.now().toString()
const str = timestamp.substring(timestamp.length - 6)
const suffix = Math.floor(Math.random() * 90 + 9)

return `${str}${suffix}`
}

module.exports = AuthUtil
/**
* Generates HMAC based on source and key.
* Source is defined as combination of clientId, originalUrl, timestamp, signKey respectively.
* @param {{clientId, originalUrl, timestamp, signKey}} options
* @param {string} key
* @return {string}
*/
const createSocketAuthHash = (options, key) => {
const { clientId, originalUrl, timestamp } = options
const hashSource = `${clientId}${originalUrl}${timestamp}`

return createHMAC(hashSource, key)
}

module.exports = {
createSocketAuthHash,
generateHash,
generateNonce
}
4 changes: 3 additions & 1 deletion lib/util/error.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ const CUSTOM_ERRORS = [
'RenderforestError',
'ScreenHasVideoAreaError',
'ScreenIdAlreadyExistsError',
'ScreenNotFoundError'
'ScreenNotFoundError',
'SocketConnectionError'
]

const ERRORS = CUSTOM_ERRORS.reduce((acc, className) => {
Expand Down Expand Up @@ -52,5 +53,6 @@ const ERRORS = CUSTOM_ERRORS.reduce((acc, className) => {
* @property ScreenHasVideoAreaError
* @property ScreenIdAlreadyExistsError
* @property ScreenNotFoundError
* @property SocketConnectionError
*/
module.exports = ERRORS
Loading

0 comments on commit d56f2e1

Please sign in to comment.