@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 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
@@ -0,0 +1,10 @@
1
+ import App from '@live-change/framework'
2
+ const app = App.app()
3
+ import user from '@live-change/user-service'
4
+
5
+ const definition = app.createServiceDefinition({
6
+ name: "phone",
7
+ use: [ user ]
8
+ })
9
+
10
+ export default definition
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
+ })