Skip to content

Commit d56f2e1

Browse files
Merge pull request #166 from renderforest/render-status
render-status
2 parents 1b0965d + 781f475 commit d56f2e1

File tree

13 files changed

+440
-67
lines changed

13 files changed

+440
-67
lines changed

README.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,11 @@ Renderforest SDK for Node.js.
33

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

6-
76
### [API Reference](https://developers.renderforest.com)
87

98
# Introduction
109

11-
Welcome to the Renderforest API!
10+
Welcome to the Renderforest API! With our SDK you can use following resources:
1211

1312
* [Projects](https://github.com/renderforest/renderforest-sdk-node/blob/master/docs/PROJECTS_API.md)
1413
* [Project-data](https://github.com/renderforest/renderforest-sdk-node/blob/master/docs/project-data-api/PROJECT_DATA_API.md)

docs/PROJECTS_API.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
- [Apply Template Preset on the Project](#apply-template-preset-on-the-project)
1111
- [Duplicate the Project](#duplicate-the-project)
1212
- [Render the Project](#render-the-project)
13+
- [Get rendering status](#get-rendering-status)
1314

1415
### Get All Projects
1516

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

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

199+
200+
### Get rendering status
201+
202+
Our rendering status method uses user's defined `callback` to manage rendering status percentage
203+
and uses error first callback pattern. If you want to unsubscribe from getting rendering status
204+
(before rendering finishes) then simply call
205+
`unsubscribe` (`getRenderingStatus` method returns function to unsubscribe from getting rendering status).
206+
207+
```js
208+
const RenderforestClient = require('@renderforest/sdk-node')
209+
210+
const Renderforest = new RenderforestClient({ signKey: '<signKey>', clientId: -1 })
211+
212+
Renderforest.renderProject(15431416, { quality: 720 })
213+
.then(() => {
214+
const unsubscribe = Renderforest.getRenderingStatus((error, percentage) => {
215+
if (error) {
216+
console.log(error)
217+
}
218+
// take percentage from here
219+
console.log(percentage)
220+
// if you want in unsubscribe (before rendering finishes) for some reason, then simply call unsubscribe
221+
unsubscribe()
222+
})
223+
})
224+
```
225+
198226
**[⬆ back to the top](#projects-api)**

docs/project-data-api/PROJECT_DATA_API.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ your changes with API. Save method can be called with chaining.
198198
Renderforest.getProjectData(15220886)
199199
.then((projectDataInstance) =>
200200
projectDataInstance.setMuteMusic(true)
201-
/// do some changes, then call save() method
201+
// do some changes, then call save() method
202202
.save()
203203
)
204204
.catch(console.error)

lib/api.js

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,21 @@
77
*/
88

99
const QueryString = require('querystring')
10-
1110
const RequestPromise = require('request-promise')
11+
const socketClient = require('socket.io-client')
12+
13+
const { createAuthorizationQuery, setAuthorization } = require('./auth')
1214

13-
const Auth = require('./auth')
15+
const {
16+
API_HOST,
17+
WEB_HOST,
18+
HTTP_DEFAULT_OPTIONS,
19+
SOCKET_HOST,
20+
REND_FINISHED_STATE,
21+
REND_NOT_FINISHED_STATE
22+
} = require('./config')
1423

15-
const { API_HOST, WEB_HOST, HTTP_DEFAULT_OPTIONS } = require('./config')
24+
const { SocketConnectionError } = require('./util/error')
1625

1726
class Api {
1827
/**
@@ -80,7 +89,6 @@ class Api {
8089
*/
8190
unauthorizedRequest (options) {
8291
const _options = Object.assign({}, HTTP_DEFAULT_OPTIONS, options)
83-
8492
Api.prepareRequest(_options)
8593

8694
return Api.request(_options)
@@ -93,9 +101,8 @@ class Api {
93101
*/
94102
authorizedRequest (options) {
95103
const _options = Object.assign({}, HTTP_DEFAULT_OPTIONS, options)
96-
97104
Api.prepareRequest(_options)
98-
Auth.setAuthorization(_options, this.signKey, this.clientId)
105+
setAuthorization(_options, this.signKey, this.clientId)
99106

100107
return Api.request(_options)
101108
}
@@ -107,11 +114,38 @@ class Api {
107114
*/
108115
webRequest (options) {
109116
const _options = Object.assign({}, HTTP_DEFAULT_OPTIONS, options)
110-
111117
Api.appendURI(_options, WEB_HOST)
112118

113119
return RequestPromise(_options)
114120
}
121+
122+
/**
123+
* @param {Function} callback - The callback function for handling render status result.
124+
* @return {unsubscribe}
125+
*/
126+
socketConnection (callback) {
127+
const renderStatus = socketClient(SOCKET_HOST, {
128+
query: createAuthorizationQuery(this.clientId, this.signKey)
129+
})
130+
131+
renderStatus.on('error', (error) => callback(new SocketConnectionError(error)))
132+
renderStatus.on('event', (status) => {
133+
if (status.state === 'rended') {
134+
renderStatus.close()
135+
return callback(null, REND_FINISHED_STATE)
136+
} else {
137+
// if `status.state` is not `rended` but percent is 100, then show rend not finished state
138+
return callback(null, Math.min(REND_NOT_FINISHED_STATE, status.percent))
139+
}
140+
})
141+
142+
// if socket is still open, then force close manually.
143+
const unsubscribe = () => {
144+
renderStatus.close()
145+
}
146+
147+
return unsubscribe
148+
}
115149
}
116150

117151
module.exports = new Api()

lib/auth.js

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,10 @@ const url = require('url')
1010

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

13+
const { SOCKET_ORIGINAL_URL } = require('./config')
14+
1315
/**
14-
* Set Authorization.
15-
* @param {Object} options
16-
* @param {string} signKey
17-
* @param {number} clientId
16+
* Set Authorization for api requests.
1817
*/
1918
const setAuthorization = (options, signKey, clientId) => {
2019
const { path, query } = url.parse(options.uri)
@@ -35,6 +34,28 @@ const setAuthorization = (options, signKey, clientId) => {
3534
options.headers = Object.assign({}, options.headers, headers)
3635
}
3736

37+
/**
38+
* Builds authorization string for socket connection.
39+
* @return {String}
40+
*/
41+
const createSocketAuthorizationString = (clientId, signKey, timestamp) => AuthUtil.createSocketAuthHash({
42+
clientId,
43+
timestamp,
44+
originalUrl: SOCKET_ORIGINAL_URL
45+
}, signKey)
46+
47+
/**
48+
* Creates authorization query based on auth, clientId and timestamp.
49+
* @return {String}
50+
*/
51+
const createAuthorizationQuery = (clientId, signKey) => {
52+
const timestamp = Date.now()
53+
const auth = createSocketAuthorizationString(clientId, signKey, timestamp)
54+
55+
return `authorization=${auth}&user_id=${clientId}&timestamp=${timestamp}`
56+
}
57+
3858
module.exports = {
59+
createAuthorizationQuery,
3960
setAuthorization
4061
}

lib/client.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,14 @@ class RenderforestClient {
115115
return Projects.renderProject(projectId, params)
116116
}
117117

118+
/**
119+
* @param {Function} callback
120+
* @return {WebSocket}
121+
*/
122+
getRenderingStatus (callback) {
123+
return Projects.getRenderingStatus(callback)
124+
}
125+
118126
/**
119127
* @param {Object} params
120128
* @returns {Promise.<Object>}

lib/config.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ const CONFIG = {
1414
}
1515
},
1616
PROJECT_DATA_API_PREFIX: '/api/v5',
17+
REND_FINISHED_STATE: 100,
18+
REND_NOT_FINISHED_STATE: 99,
19+
SOCKET_HOST: 'wss://sockets.renderforest.com',
20+
SOCKET_ORIGINAL_URL: 'sockets.renderforest.com',
1721
WEB_HOST: 'https://www.renderforest.com',
1822
WEB_PREFIX: '/api/v1'
1923
}

lib/resources/projects.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,11 @@ const renderProject = (projectId, params) => {
153153
return Api.authorizedRequest(options)
154154
}
155155

156+
/**
157+
* @param {Function} callback - The callback function for handling render status result.
158+
*/
159+
const getRenderingStatus = (callback) => Api.socketConnection(callback)
160+
156161
module.exports = {
157162
getProject,
158163
addProject,
@@ -163,5 +168,6 @@ module.exports = {
163168
deleteProjectVideos,
164169
applyTemplatePresetOnProject,
165170
duplicateProject,
166-
renderProject
171+
renderProject,
172+
getRenderingStatus
167173
}

lib/util/auth.js

Lines changed: 59 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -8,53 +8,64 @@
88

99
const crypto = require('crypto')
1010

11-
class AuthUtil {
12-
/**
13-
* Creates keyed-hash message authentication code (HMAC).
14-
* Used core `crypto` module cryptographic hash function.
15-
* Secret key - sha512.
16-
* @param {string} text
17-
* @param {string} key
18-
* @returns {string}
19-
*/
20-
static createHMAC (text, key) {
21-
return crypto.createHmac('sha512', key).update(text).digest('hex')
22-
}
23-
24-
/**
25-
* Generates HMAC based on source and key.
26-
* Source is defined as combination of clientId, path, qs, body, nonce and timestamp respectively.
27-
* @param {{clientId, qs, path, body, nonce, timestamp}} options
28-
* @param {string} key
29-
* @returns {string}
30-
*/
31-
static generateHash (options, key) {
32-
const clientId = options.clientId
33-
const qs = options.qs
34-
const path = options.path
35-
const body = options.body
36-
const nonce = options.nonce
37-
const timestamp = options.timestamp
38-
const hashSource = `${clientId}${path}${qs}${body}${nonce}${timestamp}`
39-
40-
return AuthUtil.createHMAC(hashSource, key)
41-
}
42-
43-
/**
44-
* Generates nonce.
45-
* Creates timestamp
46-
* Gets the last 6 chars of the timestamp
47-
* Generates random number between 10-99
48-
* Combined the last two ones.
49-
* @returns {string}
50-
*/
51-
static generateNonce () {
52-
const timestamp = Date.now().toString()
53-
const str = timestamp.substring(timestamp.length - 6)
54-
const suffix = Math.floor(Math.random() * 90 + 9)
55-
56-
return `${str}${suffix}`
57-
}
11+
/**
12+
* Creates keyed-hash message authentication code (HMAC).
13+
* Used core `crypto` module cryptographic hash function.
14+
* Secret key - sha512.
15+
* @param {string} text
16+
* @param {string} key
17+
* @returns {string}
18+
*/
19+
const createHMAC = (text, key) => {
20+
return crypto.createHmac('sha512', key).update(text).digest('hex')
21+
}
22+
23+
/**
24+
* Generates HMAC based on source and key.
25+
* Source is defined as combination of clientId, path, qs, body, nonce and timestamp respectively.
26+
* @param {{clientId, qs, path, body, nonce, timestamp}} options
27+
* @param {string} key
28+
* @returns {string}
29+
*/
30+
const generateHash = (options, key) => {
31+
const { clientId, qs, path, body, nonce, timestamp } = options
32+
const hashSource = `${clientId}${path}${qs}${body}${nonce}${timestamp}`
33+
34+
return createHMAC(hashSource, key)
35+
}
36+
37+
/**
38+
* Generates nonce.
39+
* Creates timestamp
40+
* Gets the last 6 chars of the timestamp
41+
* Generates random number between 10-99
42+
* Combined the last two ones.
43+
* @returns {string}
44+
*/
45+
const generateNonce = () => {
46+
const timestamp = Date.now().toString()
47+
const str = timestamp.substring(timestamp.length - 6)
48+
const suffix = Math.floor(Math.random() * 90 + 9)
49+
50+
return `${str}${suffix}`
5851
}
5952

60-
module.exports = AuthUtil
53+
/**
54+
* Generates HMAC based on source and key.
55+
* Source is defined as combination of clientId, originalUrl, timestamp, signKey respectively.
56+
* @param {{clientId, originalUrl, timestamp, signKey}} options
57+
* @param {string} key
58+
* @return {string}
59+
*/
60+
const createSocketAuthHash = (options, key) => {
61+
const { clientId, originalUrl, timestamp } = options
62+
const hashSource = `${clientId}${originalUrl}${timestamp}`
63+
64+
return createHMAC(hashSource, key)
65+
}
66+
67+
module.exports = {
68+
createSocketAuthHash,
69+
generateHash,
70+
generateNonce
71+
}

lib/util/error.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ const CUSTOM_ERRORS = [
1818
'RenderforestError',
1919
'ScreenHasVideoAreaError',
2020
'ScreenIdAlreadyExistsError',
21-
'ScreenNotFoundError'
21+
'ScreenNotFoundError',
22+
'SocketConnectionError'
2223
]
2324

2425
const ERRORS = CUSTOM_ERRORS.reduce((acc, className) => {
@@ -52,5 +53,6 @@ const ERRORS = CUSTOM_ERRORS.reduce((acc, className) => {
5253
* @property ScreenHasVideoAreaError
5354
* @property ScreenIdAlreadyExistsError
5455
* @property ScreenNotFoundError
56+
* @property SocketConnectionError
5557
*/
5658
module.exports = ERRORS

0 commit comments

Comments
 (0)