rejoiner 1.2.0 → 2.10.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -17,89 +17,138 @@ npm install rejoiner --save
17
17
  ````js
18
18
  var Rejoiner = require('rejoiner')
19
19
 
20
- var apiClient = new Rejoiner({
21
- siteId: '527bcca942bd247ca1816847',
22
- domain: '173.203.96.102',
23
- apiKey: '63cab4a5e1c44fcbbe260daa3f0bc55d',
24
- apiSecret: 'cb015c09a3b54688a247470b462082b3',
20
+ var client = new Rejoiner({
21
+ // Your Site ID
22
+ siteId: 'eXaMpLe',
23
+ // Your API key
24
+ apiKey: 'tHiSaPiKeYiSjUsTaNeXaMpLeAnDyOuCaNtUsEiT',
25
25
  })
26
26
  ````
27
27
 
28
- ## Endpoints
28
+ ## Ping
29
29
 
30
- ### Convert Lead
30
+ The `ping` endpoint can be used to verify your credentials are working.
31
31
 
32
32
  ````js
33
- apiClient.lead.convert({
34
- email: 'foo2@bar.com',
33
+ client.verify.ping()
34
+ .then(...)
35
+ .catch(...)
36
+ ````
37
+
38
+ ## Customer Endpoints
39
+
40
+ ### Convert Customer
41
+
42
+ ````js
43
+ client.customer.convert({
44
+ email: 'test@example.com',
35
45
  cart_data: {
36
- cart_value: 79996,
46
+ cart_value: 20000,
37
47
  cart_item_count: 2,
48
+ promo: 'COUPON_CODE',
49
+ return_url: 'https://www.example.com/return_url',
50
+ ...
38
51
  },
39
- cart_items: [{
40
- 'name': 'Item Name',
41
- 'product_id': 'ITM1',
42
- 'price': 19999,
43
- 'product_url': 'http://yoursite.com/productpage',
44
- 'category': ['televisions', 'smart_tv'],
45
- 'item_qty': 2,
46
- 'qty_price': 39998,
47
- 'image_url': 'http://yoursite.com/path/to/image.jpg'
48
- }, {
49
- 'name': 'Item Name2',
50
- 'product_id': 'ITM2',
51
- 'price': 19999,
52
- 'product_url': 'http://yoursite.com/productpage2',
53
- 'category': ['televisions'],
54
- 'item_qty': 2,
55
- 'qty_price': 39998,
56
- 'image_url': 'http://yoursite.com/path/to/image2.jpg'
57
- }],
52
+ cart_items: [
53
+ {
54
+ product_id: 'example',
55
+ name: 'Example Product',
56
+ price: 10000,
57
+ description: 'Information about Example Product.',
58
+ category: [
59
+ 'Example Category 1',
60
+ 'Example Category 2',
61
+ ],
62
+ item_qty: 1,
63
+ qty_price: 10000,
64
+ product_url: 'https://www.example.com/products/example',
65
+ image_url: 'https://www.example.com/products/example/images/example.jpg',
66
+ ...
67
+ },
68
+ {
69
+ product_id: 'example2',
70
+ name: 'Example Product 2',
71
+ price: 10000,
72
+ description: 'Information about Example Product 2.',
73
+ category: [
74
+ 'Example Category 2',
75
+ 'Example Category 3',
76
+ ],
77
+ item_qty: 1,
78
+ qty_price: 10000,
79
+ product_url: 'https://www.example.com/products/example2',
80
+ image_url: 'https://www.example.com/products/example2/images/example.jpg',
81
+ ...
82
+ },
83
+ ...
84
+ ],
58
85
  })
59
86
  .then(...)
60
87
  .catch(...)
61
88
  ````
62
89
 
63
- ### Cancellation
90
+ ### Journey Cancellation
64
91
 
65
92
  ````js
66
- apiClient.lead.cancel('foo@bar.com')
93
+ client.customer.cancel('test@example.com')
67
94
  .then(...)
68
95
  .catch(...)
69
96
  ````
70
97
 
71
- ### Adding Contact to List
98
+ ### Customer Unsubscribe
72
99
 
73
100
  ````js
74
- apiClient.contact.add({
75
- email: 'foo@bar.com',
76
- list_id: '5706374ae6ec520d3370e368',
77
- first_name: 'Tom',
78
- })
101
+ client.customer.unsubscribe('test@example.com')
102
+ .then(...)
103
+ .catch(...)
104
+ ````
105
+
106
+ ### Record Explicit Customer Consent
107
+
108
+ ````js
109
+ client.customer.optIn('test@example.com')
110
+ .then(...)
111
+ .catch(...)
112
+ ````
113
+
114
+ ## Email List Endpoints
115
+
116
+ ### Email Lists
117
+
118
+ ````js
119
+ client.lists.get()
120
+ .then(...)
121
+ .catch(...)
122
+ ````
123
+
124
+ ### Retrieving Listing of Contacts
125
+
126
+ ````js
127
+ client.lists.contacts('eXaMpLeLiStId').get()
79
128
  .then(...)
80
129
  .catch(...)
81
130
  ````
82
131
 
83
- ### Unsubscribe Contact
132
+ #### With optional page number for pagination
84
133
 
85
134
  ````js
86
- apiClient.lead.unsubscribe('foo@bar.com')
135
+ client.lists.contacts('eXaMpLeLiStId').get(2)
87
136
  .then(...)
88
137
  .catch(...)
89
138
  ````
90
139
 
91
- ### Retrieve Lists
140
+ ### Add Customer to List
92
141
 
93
142
  ````js
94
- apiClient.lists.get()
143
+ client.lists.contacts('eXaMpLeLiStId').add('test@example.com')
95
144
  .then(...)
96
145
  .catch(...)
97
146
  ````
98
147
 
99
- ### Record Contact Opt In
148
+ ### Remove Customer From List
100
149
 
101
150
  ````js
102
- apiClient.lead.optIn('foo@bar.com')
151
+ client.lists.contacts('eXaMpLeLiStId').remove('test@example.com')
103
152
  .then(...)
104
153
  .catch(...)
105
154
  ````
package/lib/config.js CHANGED
@@ -1,10 +1,14 @@
1
1
  const VERSION = require('../package.json').version
2
2
 
3
- const { REJOINER_API_KEY, REJOINER_API_SECRET, REJOINER_SITE_ID } = process.env
3
+ const { REJOINER_API_KEY, REJOINER_SITE_ID, REJOINER_WEBHOOK_SECRET } = process.env
4
+
5
+ const DEFAULT_BASE_URL = 'https://rj2.rejoiner.com/api/v1'
6
+ const REJOINER_BASE_URL = process.env.REJOINER_BASE_URL || DEFAULT_BASE_URL
4
7
 
5
8
  module.exports = {
6
9
  VERSION,
7
10
  REJOINER_API_KEY,
8
- REJOINER_API_SECRET,
9
11
  REJOINER_SITE_ID,
12
+ REJOINER_WEBHOOK_SECRET,
13
+ REJOINER_BASE_URL,
10
14
  }
@@ -0,0 +1,22 @@
1
+ const { withClient } = require('../helpers')
2
+
3
+ const customerEndpoint = (client) => {
4
+ const endpoint = 'customer'
5
+
6
+ const { dispatchReturnData, postEmail, putEmail } = withClient(client)
7
+
8
+ return {
9
+ path: endpoint,
10
+ convert: (data, always) => {
11
+ if (always) return dispatchReturnData('post')(`${endpoint}/always_convert/`, data)
12
+ return dispatchReturnData('post')(`${endpoint}/convert/`, data)
13
+ },
14
+ cancel: email => postEmail(`${endpoint}/cancel/`, email),
15
+ unsubscribe: email => postEmail(`${endpoint}/unsubscribe/`, email),
16
+ optIn: email => postEmail(`${endpoint}/opt_in/`, email),
17
+ get: email => dispatchReturnData('get')(`customers/${email}/`),
18
+ update: data => putEmail(`customers/${data.email}/`, data),
19
+ }
20
+ }
21
+
22
+ module.exports = customerEndpoint
@@ -0,0 +1,39 @@
1
+ const { withClient } = require('../helpers')
2
+
3
+ const path = 'journeys'
4
+
5
+ const journeysEndpoint = (client) => {
6
+ const { dispatchReturnData } = withClient(client)
7
+
8
+ const endpoint = journeyId => ({
9
+ nodes: nodeId => ({
10
+ webhook: (email) => {
11
+ const requestPath = `${path}/${journeyId}/nodes/${nodeId}/webhook_event_wait/`
12
+
13
+ if (typeof email === 'string') {
14
+ return dispatchReturnData('post')(requestPath, {
15
+ email: email.toLowerCase(),
16
+ customer_data: {},
17
+ session_data: {},
18
+ })
19
+ }
20
+
21
+ if (typeof email === 'object' && typeof email.email === 'string') {
22
+ return dispatchReturnData('post')(requestPath, Object.assign(
23
+ { customer_data: {}, session_data: {} },
24
+ email,
25
+ { email: email.email.toLowerCase() },
26
+ ))
27
+ }
28
+
29
+ return Promise.reject(new Error('Request is missing required email parameter.'))
30
+ },
31
+ }),
32
+ })
33
+
34
+ endpoint.path = path
35
+
36
+ return endpoint
37
+ }
38
+
39
+ module.exports = journeysEndpoint
@@ -1,10 +1,28 @@
1
+ const { withClient } = require('../helpers')
2
+
1
3
  const listsEndpoint = (client) => {
2
4
  const endpoint = 'lists'
3
5
 
6
+ const { dispatchReturnData, postEmail } = withClient(client)
7
+
4
8
  return {
5
- name: endpoint,
6
- get: () => client.dispatch.get(endpoint)
7
- .then(res => res.data),
9
+ path: endpoint,
10
+ get: () => dispatchReturnData('get')(`${endpoint}/`),
11
+ add: (name) => {
12
+ if (typeof name === 'string') {
13
+ return dispatchReturnData('post')(`${endpoint}/`, { name })
14
+ }
15
+
16
+ return dispatchReturnData('post')(`${endpoint}/`, name)
17
+ },
18
+ contacts: listId => ({
19
+ get: (page) => {
20
+ const pagination = page ? `?page=${page}` : ''
21
+ return dispatchReturnData('get')(`${endpoint}/${listId}/contacts/${pagination}`)
22
+ },
23
+ add: email => postEmail(`${endpoint}/${listId}/contacts/`, email),
24
+ remove: email => postEmail(`${endpoint}/${listId}/contacts/remove/`, email),
25
+ }),
8
26
  }
9
27
  }
10
28
 
@@ -0,0 +1,11 @@
1
+ const pingEndpoint = (client) => {
2
+ const endpoint = 'verify'
3
+
4
+ return {
5
+ path: endpoint,
6
+ ping: () => client.dispatch.get('/ping/')
7
+ .then(res => res.data),
8
+ }
9
+ }
10
+
11
+ module.exports = pingEndpoint
@@ -0,0 +1,16 @@
1
+ const { withClient } = require('../helpers')
2
+
3
+ const segmentsEndpoint = (client) => {
4
+ const endpoint = 'segments'
5
+
6
+ const { dispatchReturnData } = withClient(client)
7
+
8
+ return {
9
+ path: endpoint,
10
+ customers: segmentId => ({
11
+ get: () => dispatchReturnData('get')(`${endpoint}/${segmentId}/customers/`),
12
+ }),
13
+ }
14
+ }
15
+
16
+ module.exports = segmentsEndpoint
package/lib/helpers.js ADDED
@@ -0,0 +1,31 @@
1
+ const withClient = (client) => {
2
+ const dispatchReturnData = method => (...args) => client.dispatch[method](...args)
3
+ .then(res => res.data)
4
+
5
+ const withEmail = (method, path, email) => {
6
+ if (typeof email === 'string') {
7
+ return dispatchReturnData(method)(path, { email: email.toLowerCase() })
8
+ }
9
+
10
+ if (typeof email === 'object' && typeof email.email === 'string') {
11
+ return dispatchReturnData(method)(path, Object.assign(
12
+ {},
13
+ email,
14
+ { email: email.email.toLowerCase() },
15
+ ))
16
+ }
17
+
18
+ return Promise.reject(new Error('Request is missing required email parameter.'))
19
+ }
20
+
21
+ const postEmail = (path, email) => withEmail('post', path, email)
22
+ const putEmail = (path, email) => withEmail('put', path, email)
23
+
24
+ return {
25
+ dispatchReturnData,
26
+ postEmail,
27
+ putEmail,
28
+ }
29
+ }
30
+
31
+ module.exports = { withClient }
package/lib/rejoiner.js CHANGED
@@ -2,57 +2,67 @@ const axios = require('axios')
2
2
  const crypto = require('crypto')
3
3
  const fs = require('graceful-fs')
4
4
  const path = require('path')
5
- const { merge } = require('lodash')
5
+ const merge = require('lodash.merge')
6
6
 
7
7
  const {
8
8
  VERSION,
9
9
  REJOINER_API_KEY,
10
- REJOINER_API_SECRET,
11
10
  REJOINER_SITE_ID,
11
+ REJOINER_WEBHOOK_SECRET,
12
+ REJOINER_BASE_URL,
12
13
  } = require('./config')
13
14
 
14
- function Rejoiner(options) {
15
+ function Rejoiner2(options) {
15
16
  const opts = merge({}, options)
16
17
 
17
18
  this.siteId = opts.siteId || REJOINER_SITE_ID
18
19
  this.apiKey = opts.apiKey || REJOINER_API_KEY
19
- this.apiSecret = opts.apiSecret || REJOINER_API_SECRET
20
+ this.webhookSecret = opts.webhookSecret || REJOINER_WEBHOOK_SECRET
21
+ this.baseURL = opts.baseURL || REJOINER_BASE_URL
20
22
 
21
- this.sign = ({ httpVerb, requestPath, requestBody }) => {
22
- const req = [httpVerb, requestPath, requestBody].join('\n')
23
- return crypto.createHmac('sha1', this.apiSecret).update(req).digest('base64')
24
- }
23
+ if (!this.siteId) throw new Error('Site ID must be configured')
24
+ if (!this.apiKey) throw new Error('API Key must be configured')
25
25
 
26
26
  this.dispatch = axios.create({
27
- baseURL: `https://app.rejoiner.com/api/1.0/site/${this.siteId}`,
27
+ baseURL: `${this.baseURL}/${this.siteId}`,
28
28
  headers: {
29
- 'User-Agent': `rejoiner-node/v${VERSION}`,
29
+ Authorization: `Rejoiner ${this.apiKey}`,
30
+ 'User-Agent': `rejoiner2-node/v${VERSION}`,
30
31
  },
31
32
  })
32
33
 
33
- this.dispatch.interceptors.request.use((conf) => {
34
- const signedReq = this.sign({
35
- httpVerb: conf.method.toUpperCase(),
36
- requestPath: conf.url.replace('https://app.rejoiner.com', ''),
37
- requestBody: JSON.stringify(conf.data),
38
- })
39
-
40
- const withSignedReqHeader = merge({}, conf, {
41
- headers: {
42
- Authorization: `Rejoiner ${this.apiKey}:${signedReq}`,
43
- },
44
- })
45
-
46
- return withSignedReqHeader
47
- })
48
-
49
34
  fs.readdirSync(path.join(__dirname, 'endpoints'))
50
35
  .filter(file => file.indexOf('.') !== 0)
51
36
  .forEach((file) => {
52
37
  // eslint-disable-next-line global-require, import/no-dynamic-require
53
38
  const endpoint = require(path.join(__dirname, 'endpoints', file))(this)
54
- this[endpoint.name] = endpoint
39
+ this[endpoint.path] = endpoint
55
40
  })
41
+
42
+ this.verifyWebhook = (signatureHeader, payload) => {
43
+ if (!this.webhookSecret) throw new Error('No webhook secret configured')
44
+
45
+ const { timestamp, hmac } = signatureHeader.split(',')
46
+ .reduce((signature, element) => {
47
+ const [key, value] = element.trim().split('=')
48
+ switch (key) {
49
+ case 't':
50
+ return { ...signature, timestamp: value }
51
+ case 'sha1':
52
+ return { ...signature, hmac: value }
53
+ default:
54
+ return signature
55
+ }
56
+ }, {})
57
+
58
+ const signedPayload = `${timestamp}.${payload}`
59
+
60
+ const digest = crypto.createHmac('sha1', this.webhookSecret)
61
+ .update(signedPayload)
62
+ .digest('hex')
63
+
64
+ return digest === hmac
65
+ }
56
66
  }
57
67
 
58
- module.exports = Rejoiner
68
+ module.exports = Rejoiner2
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "rejoiner",
3
3
  "description": "Rejoiner REST API client wrapper for Node.js",
4
- "version": "1.2.0",
4
+ "version": "2.10.2",
5
5
  "main": "lib/rejoiner.js",
6
6
  "author": "Sascha Bratton <sascha@brattonbratton.com>",
7
7
  "license": "MIT",
@@ -26,12 +26,15 @@
26
26
  "dependencies": {
27
27
  "axios": ">=0.18.1",
28
28
  "graceful-fs": "^4.1.11",
29
- "lodash": "^4.17.13"
29
+ "lodash.merge": "^4.6.2"
30
30
  },
31
31
  "devDependencies": {
32
32
  "eslint": "^5.3.0",
33
33
  "eslint-config-airbnb-base": "^13.2.0",
34
34
  "eslint-plugin-import": "^2.17.2",
35
35
  "husky": "^0.14.3"
36
+ },
37
+ "engines": {
38
+ "node": ">=8.3.0"
36
39
  }
37
40
  }
@@ -1,11 +0,0 @@
1
- const contactAddEndpoint = (client) => {
2
- const endpoint = 'contact'
3
-
4
- return {
5
- name: endpoint,
6
- add: data => client.dispatch.post(`${endpoint}_add`, data)
7
- .then(res => res.data),
8
- }
9
- }
10
-
11
- module.exports = contactAddEndpoint
@@ -1,26 +0,0 @@
1
- const leadEndpoint = (client) => {
2
- const endpoint = 'lead'
3
-
4
- const postEmail = (path, email) => {
5
- let data
6
-
7
- if (typeof email === 'string') {
8
- data = { email }
9
- } else {
10
- data = email
11
- }
12
-
13
- return client.dispatch.post(path, data).then(res => res.data)
14
- }
15
-
16
- return {
17
- name: endpoint,
18
- convert: data => client.dispatch.post(`${endpoint}/convert`, data)
19
- .then(res => res.data),
20
- cancel: email => postEmail(`${endpoint}/cancel`, email),
21
- optIn: email => postEmail(`${endpoint}/opt_in`, email),
22
- unsubscribe: email => postEmail(`${endpoint}/unsubscribe`, email),
23
- }
24
- }
25
-
26
- module.exports = leadEndpoint