rejoiner 1.1.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 ADDED
@@ -0,0 +1,154 @@
1
+ # Rejoiner Node.js client wrapper
2
+
3
+ ## Install
4
+
5
+ ````bash
6
+ yarn add rejoiner
7
+ ````
8
+
9
+ or
10
+
11
+ ````bash
12
+ npm install rejoiner --save
13
+ ````
14
+
15
+ ## Use
16
+
17
+ ````js
18
+ var Rejoiner = require('rejoiner')
19
+
20
+ var client = new Rejoiner({
21
+ // Your Site ID
22
+ siteId: 'eXaMpLe',
23
+ // Your API key
24
+ apiKey: 'tHiSaPiKeYiSjUsTaNeXaMpLeAnDyOuCaNtUsEiT',
25
+ })
26
+ ````
27
+
28
+ ## Ping
29
+
30
+ The `ping` endpoint can be used to verify your credentials are working.
31
+
32
+ ````js
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',
45
+ cart_data: {
46
+ cart_value: 20000,
47
+ cart_item_count: 2,
48
+ promo: 'COUPON_CODE',
49
+ return_url: 'https://www.example.com/return_url',
50
+ ...
51
+ },
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
+ ],
85
+ })
86
+ .then(...)
87
+ .catch(...)
88
+ ````
89
+
90
+ ### Journey Cancellation
91
+
92
+ ````js
93
+ client.customer.cancel('test@example.com')
94
+ .then(...)
95
+ .catch(...)
96
+ ````
97
+
98
+ ### Customer Unsubscribe
99
+
100
+ ````js
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()
128
+ .then(...)
129
+ .catch(...)
130
+ ````
131
+
132
+ #### With optional page number for pagination
133
+
134
+ ````js
135
+ client.lists.contacts('eXaMpLeLiStId').get(2)
136
+ .then(...)
137
+ .catch(...)
138
+ ````
139
+
140
+ ### Add Customer to List
141
+
142
+ ````js
143
+ client.lists.contacts('eXaMpLeLiStId').add('test@example.com')
144
+ .then(...)
145
+ .catch(...)
146
+ ````
147
+
148
+ ### Remove Customer From List
149
+
150
+ ````js
151
+ client.lists.contacts('eXaMpLeLiStId').remove('test@example.com')
152
+ .then(...)
153
+ .catch(...)
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,11 +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: () =>
7
- client.dispatch.get(endpoint)
8
- .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
+ }),
9
26
  }
10
27
  }
11
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.1.0",
4
+ "version": "2.10.2",
5
5
  "main": "lib/rejoiner.js",
6
6
  "author": "Sascha Bratton <sascha@brattonbratton.com>",
7
7
  "license": "MIT",
@@ -24,14 +24,17 @@
24
24
  "prepush": "yarn test"
25
25
  },
26
26
  "dependencies": {
27
- "axios": "^0.16.2",
27
+ "axios": ">=0.18.1",
28
28
  "graceful-fs": "^4.1.11",
29
- "lodash": "^4.17.4"
29
+ "lodash.merge": "^4.6.2"
30
30
  },
31
31
  "devDependencies": {
32
- "eslint": "^4.9.0",
33
- "eslint-config-airbnb-base": "^12.1.0",
34
- "eslint-plugin-import": "^2.7.0",
32
+ "eslint": "^5.3.0",
33
+ "eslint-config-airbnb-base": "^13.2.0",
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,12 +0,0 @@
1
- const contactAddEndpoint = (client) => {
2
- const endpoint = 'contact'
3
-
4
- return {
5
- name: endpoint,
6
- add: data =>
7
- client.dispatch.post(`${endpoint}_add`, data)
8
- .then(res => res.data),
9
- }
10
- }
11
-
12
- 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