@live-change/phone-service 0.8.13
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 +1 -0
- package/auth.js +232 -0
- package/clientPhoneValidator.js +7 -0
- package/definition.js +10 -0
- package/index.js +13 -0
- package/package.json +33 -0
- package/phoneValidator.js +7 -0
- package/render.js +40 -0
- package/send.js +121 -0
package/README.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# smsapi-service
|
package/auth.js
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import App from '@live-change/framework'
|
|
2
|
+
const validation = App.validation
|
|
3
|
+
import definition from './definition.js'
|
|
4
|
+
|
|
5
|
+
const User = definition.foreignModel('user', 'User')
|
|
6
|
+
const Phone = definition.model({
|
|
7
|
+
name: 'Phone',
|
|
8
|
+
properties: {
|
|
9
|
+
phone: {
|
|
10
|
+
type: String,
|
|
11
|
+
validation: ['nonEmpty', 'phone']
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
userItem: {
|
|
15
|
+
userReadAccess: () => true
|
|
16
|
+
}
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
definition.view({
|
|
20
|
+
name: "userPhones",
|
|
21
|
+
global: true,
|
|
22
|
+
internal: true,
|
|
23
|
+
properties: {
|
|
24
|
+
user: {
|
|
25
|
+
type: String
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
returns: {
|
|
29
|
+
type: Object
|
|
30
|
+
},
|
|
31
|
+
async daoPath({ user }) {
|
|
32
|
+
return Phone.indexRangePath('byUser', [user])
|
|
33
|
+
}
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
definition.event({
|
|
37
|
+
name: 'phoneConnected',
|
|
38
|
+
properties: {
|
|
39
|
+
phone: {
|
|
40
|
+
type: String,
|
|
41
|
+
validation: ['nonEmpty', 'phone']
|
|
42
|
+
},
|
|
43
|
+
user: {
|
|
44
|
+
type: User,
|
|
45
|
+
validation: ['nonEmpty']
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
async execute({ user, phone }) {
|
|
49
|
+
await Phone.create({
|
|
50
|
+
id: phone,
|
|
51
|
+
user, phone
|
|
52
|
+
})
|
|
53
|
+
}
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
definition.event({
|
|
57
|
+
name: "phoneDisconnected",
|
|
58
|
+
properties: {
|
|
59
|
+
phone: {
|
|
60
|
+
type: String,
|
|
61
|
+
validation: ['nonEmpty', 'phone']
|
|
62
|
+
},
|
|
63
|
+
user: {
|
|
64
|
+
type: User,
|
|
65
|
+
validation: ['nonEmpty']
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
async execute({ user, phone }) {
|
|
69
|
+
await Phone.delete(phone)
|
|
70
|
+
}
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
definition.event({
|
|
74
|
+
name: "userDeleted",
|
|
75
|
+
properties: {
|
|
76
|
+
user: {
|
|
77
|
+
type: User,
|
|
78
|
+
validation: ['nonEmpty']
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
async execute({ user }) {
|
|
82
|
+
const phones = await Phone.indexRangeGet('byUser', user)
|
|
83
|
+
await Promise.all(phones.map(phone => Phone.delete(phone)))
|
|
84
|
+
}
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
definition.trigger({
|
|
88
|
+
name: "checkNewPhone",
|
|
89
|
+
properties: {
|
|
90
|
+
phone: {
|
|
91
|
+
type: String,
|
|
92
|
+
validation: ['nonEmpty', 'phone']
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
async execute({ phone }, context, emit) {
|
|
96
|
+
const phoneData = await Phone.get(phone)
|
|
97
|
+
if(phoneData) throw { properties: { phone: 'taken' } }
|
|
98
|
+
return true
|
|
99
|
+
}
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
definition.trigger({
|
|
103
|
+
name: "getPhone",
|
|
104
|
+
properties: {
|
|
105
|
+
phone: {
|
|
106
|
+
type: String,
|
|
107
|
+
validation: ['nonEmpty', 'phone']
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
async execute({ phone }, context, emit) {
|
|
111
|
+
const phoneData = await Phone.get(phone)
|
|
112
|
+
if(!phoneData) throw { properties: { phone: 'notFound' } }
|
|
113
|
+
return phoneData
|
|
114
|
+
}
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
definition.trigger({
|
|
118
|
+
name: "getPhoneOrNull",
|
|
119
|
+
properties: {
|
|
120
|
+
phone: {
|
|
121
|
+
type: String,
|
|
122
|
+
validation: ['nonEmpty', 'phone']
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
async execute({ phone }, context, emit) {
|
|
126
|
+
const phoneData = await Phone.get(phone)
|
|
127
|
+
return phoneData
|
|
128
|
+
}
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
definition.trigger({
|
|
132
|
+
name: "connectPhone",
|
|
133
|
+
properties: {
|
|
134
|
+
phone: {
|
|
135
|
+
type: String,
|
|
136
|
+
validation: ['nonEmpty', 'phone']
|
|
137
|
+
},
|
|
138
|
+
user: {
|
|
139
|
+
type: User,
|
|
140
|
+
validation: ['nonEmpty']
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
async execute({ user, phone }, { service }, emit) {
|
|
144
|
+
if(!phone) throw new Error("no phone")
|
|
145
|
+
const phoneData = await Phone.get(phone)
|
|
146
|
+
if(phoneData) throw { properties: { phone: 'taken' } }
|
|
147
|
+
await service.trigger({
|
|
148
|
+
type: 'contactConnected',
|
|
149
|
+
contactType: 'phone_Phone',
|
|
150
|
+
contact: phone,
|
|
151
|
+
user
|
|
152
|
+
})
|
|
153
|
+
emit({
|
|
154
|
+
type: 'phoneConnected',
|
|
155
|
+
user, phone
|
|
156
|
+
})
|
|
157
|
+
return true
|
|
158
|
+
}
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
definition.trigger({
|
|
162
|
+
name: "disconnectPhone",
|
|
163
|
+
properties: {
|
|
164
|
+
phone: {
|
|
165
|
+
type: String,
|
|
166
|
+
validation: ['nonEmpty', 'phone']
|
|
167
|
+
},
|
|
168
|
+
user: {
|
|
169
|
+
type: User,
|
|
170
|
+
validation: ['nonEmpty']
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
async execute({ user, phone }, { client, service }, emit) {
|
|
174
|
+
const phoneData = await Phone.get(phone)
|
|
175
|
+
if(!phoneData) throw { properties: { phone: 'notFound' } }
|
|
176
|
+
if(phoneData.user != user) throw { properties: { phone: 'notFound' } }
|
|
177
|
+
emit({
|
|
178
|
+
type: 'phoneDisconnected',
|
|
179
|
+
user, phone
|
|
180
|
+
})
|
|
181
|
+
return true
|
|
182
|
+
}
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
definition.trigger({
|
|
186
|
+
name: "signInPhone",
|
|
187
|
+
properties: {
|
|
188
|
+
phone: {
|
|
189
|
+
type: String
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
async execute({ phone, session }, { client, service }, emit) {
|
|
193
|
+
const phoneData = await Phone.get(phone)
|
|
194
|
+
if(!phoneData) throw { properties: { phone: 'notFound' } }
|
|
195
|
+
const { user } = phoneData
|
|
196
|
+
return service.trigger({
|
|
197
|
+
type: 'signIn',
|
|
198
|
+
user, session
|
|
199
|
+
})
|
|
200
|
+
}
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
definition.trigger({
|
|
204
|
+
name: "getConnectedContacts",
|
|
205
|
+
properties: {
|
|
206
|
+
user: {
|
|
207
|
+
type: User,
|
|
208
|
+
validation: ['nonEmpty', 'phone']
|
|
209
|
+
}
|
|
210
|
+
},
|
|
211
|
+
async execute({ user }, context, emit) {
|
|
212
|
+
const phones = await Phone.indexRangeGet('byUser', user)
|
|
213
|
+
return phones.map(phone => ({ ...phone, type: 'phone', contact: phone.phone }))
|
|
214
|
+
}
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
definition.trigger({
|
|
218
|
+
name: 'userDeleted',
|
|
219
|
+
properties: {
|
|
220
|
+
user: {
|
|
221
|
+
type: User
|
|
222
|
+
}
|
|
223
|
+
},
|
|
224
|
+
async execute({ user }, { service }, emit) {
|
|
225
|
+
emit([{
|
|
226
|
+
type: "userDeleted",
|
|
227
|
+
user
|
|
228
|
+
}])
|
|
229
|
+
}
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
export { Phone }
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export default (settings) => (phone) => {
|
|
2
|
+
const digits = phone.match(/\d/g)
|
|
3
|
+
if (!digits) return "wrongPhone"
|
|
4
|
+
if (digits.length < 9) return "wrongPhone"
|
|
5
|
+
if (digits.length > 11) return "wrongPhone"
|
|
6
|
+
if (!phone.match(/^[0-9 +-]{0,20}$/g)) return "wrongPhone"
|
|
7
|
+
}
|
package/definition.js
ADDED
package/index.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import App from '@live-change/framework'
|
|
2
|
+
const app = App.app()
|
|
3
|
+
|
|
4
|
+
import definition from './definition.js'
|
|
5
|
+
|
|
6
|
+
import phoneValidator from './phoneValidator.js'
|
|
7
|
+
definition.validator('phone', phoneValidator)
|
|
8
|
+
|
|
9
|
+
import './send.js'
|
|
10
|
+
import './auth.js'
|
|
11
|
+
export default definition
|
|
12
|
+
|
|
13
|
+
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@live-change/phone-service",
|
|
3
|
+
"version": "0.8.13",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "NODE_ENV=test tape tests/*"
|
|
8
|
+
},
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/live-change/live-change-stack.git"
|
|
12
|
+
},
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/live-change/live-change-stack/issues"
|
|
16
|
+
},
|
|
17
|
+
"homepage": "https://github.com/live-change/live-change-stack",
|
|
18
|
+
"author": {
|
|
19
|
+
"email": "michal@laszczewski.pl",
|
|
20
|
+
"name": "Michał Łaszczewski",
|
|
21
|
+
"url": "https://www.viamage.com/"
|
|
22
|
+
},
|
|
23
|
+
"type": "module",
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@live-change/framework": "^0.8.13",
|
|
26
|
+
"@live-change/relations-plugin": "^0.8.13",
|
|
27
|
+
"got": "^11.8.3",
|
|
28
|
+
"html-to-text": "8.1.0",
|
|
29
|
+
"jsdom": "^18.1.1",
|
|
30
|
+
"smsapi": "2.0.9"
|
|
31
|
+
},
|
|
32
|
+
"gitHead": "4453aa639a9fe1a857d93d73ebd28a82c97fdd87"
|
|
33
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export default (settings) => (phone) => {
|
|
2
|
+
const digits = phone.match(/\d/g)
|
|
3
|
+
if (!digits) return "wrongPhone"
|
|
4
|
+
if (digits.length < 9) return "wrongPhone"
|
|
5
|
+
if (digits.length > 11) return "wrongPhone"
|
|
6
|
+
if (!phone.match(/^[0-9 +-]{0,20}$/g)) return "wrongPhone"
|
|
7
|
+
}
|
package/render.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import got from 'got'
|
|
2
|
+
import { JSDOM } from "jsdom"
|
|
3
|
+
import { convert as htmlToText } from 'html-to-text'
|
|
4
|
+
|
|
5
|
+
import definition from './definition.js'
|
|
6
|
+
const config = definition.config
|
|
7
|
+
|
|
8
|
+
async function renderSms(data) {
|
|
9
|
+
const baseUrl = `http://${config.ssrHost||process.env.SSR_HOST||'localhost'}`+
|
|
10
|
+
`:${config.ssrPort||process.env.SSR_PORT||'8001'}`
|
|
11
|
+
|
|
12
|
+
const encodedData = encodeURIComponent(JSON.stringify(data))
|
|
13
|
+
const url = `${baseUrl}/_sms/${data.action}/${data.contact}/${encodedData}`
|
|
14
|
+
console.log("RENDER SMS", data, "URL", url)
|
|
15
|
+
const response = await got(url)
|
|
16
|
+
let body = response.body
|
|
17
|
+
console.log("BASE URL", baseUrl)
|
|
18
|
+
//console.log("HTML", body)
|
|
19
|
+
const dom = new JSDOM(body)
|
|
20
|
+
const headers = JSON.parse(dom.window.document.querySelector('[data-headers]').textContent)
|
|
21
|
+
const messageElements = dom.window.document.querySelectorAll("[data-text]")
|
|
22
|
+
const sms = { ...headers }
|
|
23
|
+
for(let messageElement of messageElements) {
|
|
24
|
+
const toText = messageElement.getAttribute('data-text')
|
|
25
|
+
if(toText !== null) {
|
|
26
|
+
sms.text = htmlToText(messageElement.outerHTML)
|
|
27
|
+
if(messageElement.tagName === 'PRE') {
|
|
28
|
+
const indentation = sms.text.match(/^ */)[0]
|
|
29
|
+
const indentationRegex = new RegExp('\n' + indentation, 'g')
|
|
30
|
+
sms.text = sms.text.slice(indentation.length).replace(indentationRegex, '\n').trim()
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
console.log("sms", sms)
|
|
36
|
+
|
|
37
|
+
return sms
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export default renderSms
|
package/send.js
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import definition from './definition.js'
|
|
2
|
+
const config = definition.config
|
|
3
|
+
|
|
4
|
+
import App from '@live-change/framework'
|
|
5
|
+
const app = App.app()
|
|
6
|
+
|
|
7
|
+
import renderSms from './render.js'
|
|
8
|
+
|
|
9
|
+
import { SMSAPI } from 'smsapi'
|
|
10
|
+
const smsapi = new SMSAPI(config.accessToken || process.env.SMSAPI_ACCESS_TOKEN)
|
|
11
|
+
|
|
12
|
+
const SentSms = definition.model({
|
|
13
|
+
name: "SentSms",
|
|
14
|
+
properties: {
|
|
15
|
+
contnet: {
|
|
16
|
+
type: Object
|
|
17
|
+
},
|
|
18
|
+
error: {
|
|
19
|
+
type: Object
|
|
20
|
+
},
|
|
21
|
+
result: {
|
|
22
|
+
type: Object
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
definition.trigger({
|
|
28
|
+
name: "sendPhoneMessage",
|
|
29
|
+
properties: {
|
|
30
|
+
message: {
|
|
31
|
+
type: String
|
|
32
|
+
},
|
|
33
|
+
content: {
|
|
34
|
+
type: Object
|
|
35
|
+
},
|
|
36
|
+
render: {
|
|
37
|
+
type: Object
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
async execute({ message, content, render }, context, emit) {
|
|
41
|
+
if(render) {
|
|
42
|
+
content = await renderSms(render)
|
|
43
|
+
}
|
|
44
|
+
const { text, to, from } = content
|
|
45
|
+
if(!text) throw new Error('text must be defined')
|
|
46
|
+
if(!to) throw new Error('to must be defined')
|
|
47
|
+
if(!message) message = app.generateUid()
|
|
48
|
+
if(to === "+4823232323") {
|
|
49
|
+
console.log("TEST SMS", text)
|
|
50
|
+
emit({
|
|
51
|
+
type: 'sent',
|
|
52
|
+
content,
|
|
53
|
+
message: 'test-' + message,
|
|
54
|
+
result: 'test-sms'
|
|
55
|
+
})
|
|
56
|
+
return "test-sms-sent"
|
|
57
|
+
}
|
|
58
|
+
// return new Promise((resolve, reject) => {
|
|
59
|
+
smsapi.sms.sendSms(to, text, {
|
|
60
|
+
from,
|
|
61
|
+
encoding: 'utf-8',
|
|
62
|
+
maxParts: 10
|
|
63
|
+
}).then( info => {
|
|
64
|
+
emit({
|
|
65
|
+
type: 'sent',
|
|
66
|
+
content,
|
|
67
|
+
message,
|
|
68
|
+
result: info
|
|
69
|
+
})
|
|
70
|
+
//resolve("sent")
|
|
71
|
+
})
|
|
72
|
+
.catch((error) => {
|
|
73
|
+
emit({
|
|
74
|
+
type: 'error',
|
|
75
|
+
content,
|
|
76
|
+
message,
|
|
77
|
+
error
|
|
78
|
+
})
|
|
79
|
+
console.log("SMS ERROR", error)
|
|
80
|
+
//reject("sendFailed")
|
|
81
|
+
})
|
|
82
|
+
}
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
definition.event({
|
|
86
|
+
name: "sent",
|
|
87
|
+
properties: {
|
|
88
|
+
message: {
|
|
89
|
+
type: String
|
|
90
|
+
},
|
|
91
|
+
phone: {
|
|
92
|
+
type: String
|
|
93
|
+
},
|
|
94
|
+
content: {
|
|
95
|
+
type: Object
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
async execute({ message, content, result }) {
|
|
99
|
+
if(!message) message = app.generateUid()
|
|
100
|
+
await SentSms.create({ id: message, content, result })
|
|
101
|
+
}
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
definition.event({
|
|
105
|
+
name: "error",
|
|
106
|
+
properties: {
|
|
107
|
+
message: {
|
|
108
|
+
type: String
|
|
109
|
+
},
|
|
110
|
+
content: {
|
|
111
|
+
type: Object
|
|
112
|
+
},
|
|
113
|
+
error: {
|
|
114
|
+
type: Object
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
async execute({ message, content, error }) {
|
|
118
|
+
if(!message) message = app.generateUid()
|
|
119
|
+
await SentSms.create({ id: message, content, error })
|
|
120
|
+
}
|
|
121
|
+
})
|