@whitewall/blip-sdk 0.0.136 → 0.0.137
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/package.json +2 -2
- package/src/client.ts +117 -0
- package/src/index.ts +6 -0
- package/src/namespaces/account.ts +729 -0
- package/src/namespaces/activecampaign.ts +285 -0
- package/src/namespaces/analytics.ts +230 -0
- package/src/namespaces/billing.ts +17 -0
- package/src/namespaces/builder.ts +52 -0
- package/src/namespaces/configurations.ts +19 -0
- package/src/namespaces/context.ts +67 -0
- package/src/namespaces/desk.ts +679 -0
- package/src/namespaces/media.ts +39 -0
- package/src/namespaces/namespace.ts +125 -0
- package/src/namespaces/plugins.ts +223 -0
- package/src/namespaces/portal.ts +402 -0
- package/src/namespaces/scheduler.ts +88 -0
- package/src/namespaces/whatsapp.ts +383 -0
- package/src/sender/bliperror.ts +42 -0
- package/src/sender/enveloperesolver.ts +148 -0
- package/src/sender/gateway/customgatewaysender.ts +43 -0
- package/src/sender/http/httpsender.ts +94 -0
- package/src/sender/index.ts +7 -0
- package/src/sender/plugin/communication.ts +72 -0
- package/src/sender/plugin/pluginsender.ts +75 -0
- package/src/sender/security.ts +33 -0
- package/src/sender/sender.ts +145 -0
- package/src/sender/sessionnegotiator.ts +175 -0
- package/src/sender/tcp/tcpsender.ts +252 -0
- package/src/sender/throttler.ts +36 -0
- package/src/sender/websocket/websocketsender.ts +175 -0
- package/src/types/account.ts +84 -0
- package/src/types/analytics.ts +18 -0
- package/src/types/billing.ts +15 -0
- package/src/types/command.ts +47 -0
- package/src/types/commons.ts +16 -0
- package/src/types/desk.ts +51 -0
- package/src/types/envelope.ts +9 -0
- package/src/types/flow.ts +327 -0
- package/src/types/index.ts +13 -0
- package/src/types/message.ts +116 -0
- package/src/types/node.ts +86 -0
- package/src/types/notification.ts +18 -0
- package/src/types/plugins.ts +51 -0
- package/src/types/portal.ts +39 -0
- package/src/types/reason.ts +22 -0
- package/src/types/session.ts +22 -0
- package/src/types/whatsapp.ts +84 -0
- package/src/utils/odata.ts +114 -0
- package/src/utils/random.ts +3 -0
- package/src/utils/uri.ts +46 -0
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import type { Socket } from 'node:net'
|
|
2
|
+
import type {
|
|
3
|
+
Command,
|
|
4
|
+
CommandMethods,
|
|
5
|
+
CommandResponse,
|
|
6
|
+
Envelope,
|
|
7
|
+
Message,
|
|
8
|
+
MessageTypes,
|
|
9
|
+
Notification,
|
|
10
|
+
UnknownCommandResponse,
|
|
11
|
+
} from '../../types/index.ts'
|
|
12
|
+
import { BlipError } from '../bliperror.ts'
|
|
13
|
+
import { ConnectionSender, type ConnectionSenderConstructor, OpenConnectionSender } from '../sender.ts'
|
|
14
|
+
import { SessionNegotiator } from '../sessionnegotiator.ts'
|
|
15
|
+
import { EnvelopeThrottler } from '../throttler.ts'
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @remarks
|
|
19
|
+
* The TCP implementation has a known limitation where large command
|
|
20
|
+
* responses can be lost and the awaiting promise will never resolve.
|
|
21
|
+
* Unless TCP is explicitly required, prefer using {@link WebSocketSender}
|
|
22
|
+
*/
|
|
23
|
+
export class TCPSender extends OpenConnectionSender {
|
|
24
|
+
private readonly throttler = new EnvelopeThrottler()
|
|
25
|
+
private readonly connectionHandle: TCPHandle<Envelope>
|
|
26
|
+
|
|
27
|
+
constructor(options: ConstructorParameters<ConnectionSenderConstructor>[0]) {
|
|
28
|
+
super(options)
|
|
29
|
+
|
|
30
|
+
const prefix = options.tenantId ? `${options.tenantId}.` : ''
|
|
31
|
+
this.connectionHandle = new TCPHandle<Envelope>(
|
|
32
|
+
`${prefix}tcp.${this.domain}`,
|
|
33
|
+
443,
|
|
34
|
+
async (socket) => {
|
|
35
|
+
this.sessionNegotiator = new SessionNegotiator(this, (session) => {
|
|
36
|
+
socket.write(JSON.stringify(session))
|
|
37
|
+
})
|
|
38
|
+
return this.sessionNegotiator.negotiate({
|
|
39
|
+
node: options.node,
|
|
40
|
+
authentication: options.authentication,
|
|
41
|
+
})
|
|
42
|
+
},
|
|
43
|
+
(envelope: Envelope) => {
|
|
44
|
+
if (this.sessionNegotiator?.negotiating) {
|
|
45
|
+
return this.sessionNegotiator.handleEnvelope(envelope)
|
|
46
|
+
}
|
|
47
|
+
this.envelopeResolver.resolve(envelope)
|
|
48
|
+
},
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
public async sendNotification(notification: Notification): Promise<void> {
|
|
53
|
+
const socket = await this.connectionHandle.get()
|
|
54
|
+
socket.write(JSON.stringify(notification))
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
public async sendMessage<Type extends MessageTypes>(message: Message<Type>): Promise<void> {
|
|
58
|
+
await this.throttler.throttle('message')
|
|
59
|
+
|
|
60
|
+
const socket = await this.connectionHandle.get()
|
|
61
|
+
await this.sessionNegotiator?.ensurePresence()
|
|
62
|
+
|
|
63
|
+
socket.write(JSON.stringify(message))
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
public sendCommand(command: Command<CommandMethods>): Promise<unknown> {
|
|
67
|
+
return this.withRetryPolicy(async () => {
|
|
68
|
+
await this.throttler.throttle('command')
|
|
69
|
+
|
|
70
|
+
const envelopeResponsePromise = this.envelopeResolver.createEnvelopeResponsePromise(command.id)
|
|
71
|
+
|
|
72
|
+
const socket = await this.connectionHandle.get()
|
|
73
|
+
await this.sessionNegotiator?.ensurePresence(command.uri)
|
|
74
|
+
|
|
75
|
+
socket.write(JSON.stringify(command))
|
|
76
|
+
|
|
77
|
+
const response = (await envelopeResponsePromise) as CommandResponse<
|
|
78
|
+
unknown,
|
|
79
|
+
CommandMethods,
|
|
80
|
+
'success' | 'failure'
|
|
81
|
+
>
|
|
82
|
+
if (BlipError.isFailedCommandResponse(response)) {
|
|
83
|
+
throw BlipError.commandResponseToBlipError(command.uri, response)
|
|
84
|
+
} else if (response.status === 'success') {
|
|
85
|
+
return response.resource
|
|
86
|
+
} else {
|
|
87
|
+
throw new Error(`Unexpected response for command '${command.uri}': ${JSON.stringify(response)}`)
|
|
88
|
+
}
|
|
89
|
+
})
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
public async sendCommandResponse(response: UnknownCommandResponse): Promise<void> {
|
|
93
|
+
const socket = await this.connectionHandle.get()
|
|
94
|
+
socket.write(`${JSON.stringify(response)}\n`)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
public async close() {
|
|
98
|
+
this.sessionNegotiator?.finish()
|
|
99
|
+
await this.connectionHandle.close()
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
public static login = ConnectionSender.login<TCPSender>
|
|
103
|
+
|
|
104
|
+
private async withRetryPolicy<T>(fn: () => Promise<T>, retries = 10): Promise<T> {
|
|
105
|
+
try {
|
|
106
|
+
return await fn()
|
|
107
|
+
} catch (err) {
|
|
108
|
+
if (retries > 0 && err instanceof BlipError && BlipError.retryableBlipErrors.includes(err.code)) {
|
|
109
|
+
// wait before retrying
|
|
110
|
+
await new Promise((resolve) => setTimeout(resolve, Math.random() * 1000))
|
|
111
|
+
return this.withRetryPolicy(fn, retries - 1)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
throw err
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
class TCPHandle<T> {
|
|
120
|
+
private currentSocketPromise: Promise<Socket> | null = null
|
|
121
|
+
private closing = false
|
|
122
|
+
private connectionAttempts = 0
|
|
123
|
+
|
|
124
|
+
constructor(
|
|
125
|
+
host: string,
|
|
126
|
+
port: number,
|
|
127
|
+
onConnected: (socket: Socket) => Promise<void>,
|
|
128
|
+
onMessage: (message: T) => void,
|
|
129
|
+
) {
|
|
130
|
+
this.currentSocketPromise = this.connect(host, port, onConnected, onMessage)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
public get() {
|
|
134
|
+
if (!this.currentSocketPromise) {
|
|
135
|
+
throw new Error('TCP connection is not available.')
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return this.currentSocketPromise
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
public async close() {
|
|
142
|
+
if (!this.closing && this.currentSocketPromise) {
|
|
143
|
+
this.closing = true
|
|
144
|
+
const current = await this.currentSocketPromise
|
|
145
|
+
current.end()
|
|
146
|
+
current.destroy()
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
private async connect(
|
|
151
|
+
host: string,
|
|
152
|
+
port: number,
|
|
153
|
+
onConnected: (socket: Socket) => Promise<void>,
|
|
154
|
+
onMessage: (message: T) => void,
|
|
155
|
+
): Promise<Socket> {
|
|
156
|
+
const { connect } = await import('node:net')
|
|
157
|
+
|
|
158
|
+
const socket = connect({ host, port }).setEncoding('utf8')
|
|
159
|
+
let buffer = ''
|
|
160
|
+
|
|
161
|
+
await new Promise<void>((resolve) => {
|
|
162
|
+
socket.once('connect', () => {
|
|
163
|
+
resolve()
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
socket.once('error', (err) => {
|
|
167
|
+
if (!this.closing) {
|
|
168
|
+
throw err
|
|
169
|
+
}
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
socket.once('close', () => {
|
|
173
|
+
if (!this.closing) {
|
|
174
|
+
this.connectionAttempts++
|
|
175
|
+
if (this.connectionAttempts < 3) {
|
|
176
|
+
this.currentSocketPromise = this.connect(host, port, onConnected, onMessage)
|
|
177
|
+
} else {
|
|
178
|
+
throw new Error('Failed to connect/reconnect to TCP socket')
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
socket.on('data', (chunk) => {
|
|
184
|
+
const result = this.tryParseJSON(buffer + chunk)
|
|
185
|
+
buffer = result.remainingBuffer
|
|
186
|
+
|
|
187
|
+
for (const parsed of result.parsedObjects) {
|
|
188
|
+
onMessage(parsed)
|
|
189
|
+
}
|
|
190
|
+
})
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
await onConnected(socket)
|
|
194
|
+
this.connectionAttempts = 0
|
|
195
|
+
|
|
196
|
+
return socket
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
private tryParseJSON(buffer: string): { parsedObjects: Array<T>; remainingBuffer: string } {
|
|
200
|
+
const parsedObjects: Array<T> = []
|
|
201
|
+
let currBuffer = buffer
|
|
202
|
+
let startIndex = buffer.indexOf('{')
|
|
203
|
+
|
|
204
|
+
while (startIndex !== -1) {
|
|
205
|
+
let braceCount = 0
|
|
206
|
+
let inString = false
|
|
207
|
+
let escaped = false
|
|
208
|
+
let foundEnd = false
|
|
209
|
+
let endIndex = startIndex
|
|
210
|
+
|
|
211
|
+
for (let i = startIndex; i < currBuffer.length; i++) {
|
|
212
|
+
const char = currBuffer[i]
|
|
213
|
+
|
|
214
|
+
if (inString) {
|
|
215
|
+
if (escaped) {
|
|
216
|
+
escaped = false
|
|
217
|
+
} else if (char === '\\') {
|
|
218
|
+
escaped = true
|
|
219
|
+
} else if (char === '"') {
|
|
220
|
+
inString = false
|
|
221
|
+
}
|
|
222
|
+
} else if (char === '"') {
|
|
223
|
+
inString = true
|
|
224
|
+
} else if (char === '{') {
|
|
225
|
+
braceCount++
|
|
226
|
+
} else if (char === '}') {
|
|
227
|
+
braceCount--
|
|
228
|
+
// When all opened braces are closed, we found the end.
|
|
229
|
+
if (braceCount === 0) {
|
|
230
|
+
endIndex = i
|
|
231
|
+
foundEnd = true
|
|
232
|
+
break
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// If we haven't found a complete JSON object, break and wait for more data.
|
|
238
|
+
if (!foundEnd) {
|
|
239
|
+
break
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const rawMessage = currBuffer.slice(startIndex, endIndex + 1).trim()
|
|
243
|
+
const parsed = JSON.parse(rawMessage) as T
|
|
244
|
+
parsedObjects.push(parsed)
|
|
245
|
+
|
|
246
|
+
currBuffer = currBuffer.slice(endIndex + 1)
|
|
247
|
+
startIndex = currBuffer.indexOf('{')
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return { parsedObjects, remainingBuffer: currBuffer }
|
|
251
|
+
}
|
|
252
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export class EnvelopeThrottler {
|
|
2
|
+
private sent = {
|
|
3
|
+
message: {
|
|
4
|
+
max: 50,
|
|
5
|
+
started: 0,
|
|
6
|
+
count: 0,
|
|
7
|
+
},
|
|
8
|
+
command: {
|
|
9
|
+
max: 200,
|
|
10
|
+
started: 0,
|
|
11
|
+
count: 0,
|
|
12
|
+
},
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
public async throttle(type: keyof typeof this.sent): Promise<void> {
|
|
16
|
+
const throughput = this.sent[type].max * 0.9
|
|
17
|
+
const timeToReset = 1000
|
|
18
|
+
|
|
19
|
+
while (true) {
|
|
20
|
+
const elapsed = Date.now() - this.sent[type].started
|
|
21
|
+
|
|
22
|
+
if (elapsed >= timeToReset) {
|
|
23
|
+
this.sent[type].started = Date.now()
|
|
24
|
+
this.sent[type].count = 0
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (this.sent[type].count < throughput) {
|
|
28
|
+
this.sent[type].count++
|
|
29
|
+
break
|
|
30
|
+
} else {
|
|
31
|
+
const wait = timeToReset - elapsed
|
|
32
|
+
await new Promise((resolve) => setTimeout(resolve, wait))
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
Command,
|
|
3
|
+
CommandMethods,
|
|
4
|
+
CommandResponse,
|
|
5
|
+
Envelope,
|
|
6
|
+
Message,
|
|
7
|
+
MessageTypes,
|
|
8
|
+
Notification,
|
|
9
|
+
UnknownCommandResponse,
|
|
10
|
+
} from '../../types/index.ts'
|
|
11
|
+
import { BlipError } from '../bliperror.ts'
|
|
12
|
+
import { ConnectionSender, type ConnectionSenderConstructor, OpenConnectionSender } from '../sender.ts'
|
|
13
|
+
import { SessionNegotiator } from '../sessionnegotiator.ts'
|
|
14
|
+
import { EnvelopeThrottler } from '../throttler.ts'
|
|
15
|
+
|
|
16
|
+
export class WebSocketSender extends OpenConnectionSender {
|
|
17
|
+
private readonly throttler = new EnvelopeThrottler()
|
|
18
|
+
private readonly connectionHandle: WebSocketHandle<Envelope>
|
|
19
|
+
|
|
20
|
+
constructor(options: ConstructorParameters<ConnectionSenderConstructor>[0]) {
|
|
21
|
+
super(options)
|
|
22
|
+
|
|
23
|
+
const prefix = options.tenantId ? `${options.tenantId}.` : ''
|
|
24
|
+
this.connectionHandle = new WebSocketHandle<Envelope>(
|
|
25
|
+
`wss://${prefix}ws.${this.domain}`,
|
|
26
|
+
(webSocket) => {
|
|
27
|
+
this.sessionNegotiator = new SessionNegotiator(this, (session) => {
|
|
28
|
+
webSocket.send(JSON.stringify(session))
|
|
29
|
+
})
|
|
30
|
+
return this.sessionNegotiator.negotiate({
|
|
31
|
+
node: options.node,
|
|
32
|
+
authentication: options.authentication,
|
|
33
|
+
})
|
|
34
|
+
},
|
|
35
|
+
(envelope: Envelope) => {
|
|
36
|
+
if (this.sessionNegotiator?.negotiating) {
|
|
37
|
+
return this.sessionNegotiator.handleEnvelope(envelope)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
this.envelopeResolver.resolve(envelope)
|
|
41
|
+
},
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
public async sendMessage<Type extends MessageTypes>(message: Message<Type>): Promise<void> {
|
|
46
|
+
await this.throttler.throttle('message')
|
|
47
|
+
|
|
48
|
+
const webSocket = await this.connectionHandle.get()
|
|
49
|
+
await this.sessionNegotiator?.ensurePresence()
|
|
50
|
+
|
|
51
|
+
webSocket.send(JSON.stringify(message))
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
public sendCommand(command: Command<CommandMethods>): Promise<unknown> {
|
|
55
|
+
return this.withRetryPolicy(async () => {
|
|
56
|
+
await this.throttler.throttle('command')
|
|
57
|
+
|
|
58
|
+
const envelopeResponsePromise = this.envelopeResolver.createEnvelopeResponsePromise(command.id)
|
|
59
|
+
|
|
60
|
+
const webSocket = await this.connectionHandle.get()
|
|
61
|
+
await this.sessionNegotiator?.ensurePresence(command.uri)
|
|
62
|
+
webSocket.send(JSON.stringify(command))
|
|
63
|
+
|
|
64
|
+
const response = (await envelopeResponsePromise) as CommandResponse<
|
|
65
|
+
unknown,
|
|
66
|
+
CommandMethods,
|
|
67
|
+
'success' | 'failure'
|
|
68
|
+
>
|
|
69
|
+
if (BlipError.isFailedCommandResponse(response)) {
|
|
70
|
+
throw BlipError.commandResponseToBlipError(command.uri, response)
|
|
71
|
+
} else if (response.status === 'success') {
|
|
72
|
+
return response.resource
|
|
73
|
+
} else {
|
|
74
|
+
throw new Error(`Unexpected response for command '${command.uri}': ${JSON.stringify(response)}`)
|
|
75
|
+
}
|
|
76
|
+
})
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
public async sendNotification(notification: Notification): Promise<void> {
|
|
80
|
+
const webSocket = await this.connectionHandle.get()
|
|
81
|
+
webSocket.send(JSON.stringify(notification))
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
public async sendCommandResponse(response: UnknownCommandResponse): Promise<void> {
|
|
85
|
+
const webSocket = await this.connectionHandle.get()
|
|
86
|
+
webSocket.send(JSON.stringify(response))
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
public async isConnected(): Promise<boolean> {
|
|
90
|
+
const webSocket = await this.connectionHandle.get()
|
|
91
|
+
return webSocket.readyState === WebSocket.OPEN
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
public async close() {
|
|
95
|
+
this.sessionNegotiator?.finish()
|
|
96
|
+
await this.connectionHandle.close()
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
public static login = ConnectionSender.login<WebSocketSender>
|
|
100
|
+
|
|
101
|
+
private async withRetryPolicy<T>(fn: () => Promise<T>, retries = 5): Promise<T> {
|
|
102
|
+
try {
|
|
103
|
+
return await fn()
|
|
104
|
+
} catch (err) {
|
|
105
|
+
if (retries > 0 && err instanceof BlipError && BlipError.retryableBlipErrors.includes(err.code)) {
|
|
106
|
+
// wait before retrying
|
|
107
|
+
await new Promise((resolve) => setTimeout(resolve, Math.random() * 1000))
|
|
108
|
+
return this.withRetryPolicy(fn, retries - 1)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
throw err
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
class WebSocketHandle<T> {
|
|
117
|
+
private currentWebSocketPromise: Promise<WebSocket> | null = null
|
|
118
|
+
private closing = false
|
|
119
|
+
private connectionAttempts = 0
|
|
120
|
+
|
|
121
|
+
constructor(url: string, onConnected: (webSocket: WebSocket) => Promise<void>, onMessage: (message: T) => void) {
|
|
122
|
+
this.currentWebSocketPromise = this.connect(url, onConnected, onMessage)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
public get() {
|
|
126
|
+
return this.currentWebSocketPromise!
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
public async close() {
|
|
130
|
+
if (!this.closing && this.currentWebSocketPromise) {
|
|
131
|
+
this.closing = true
|
|
132
|
+
const current = await this.currentWebSocketPromise
|
|
133
|
+
current.close()
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
private async connect(
|
|
138
|
+
url: string,
|
|
139
|
+
onConnected: (webSocket: WebSocket) => Promise<void>,
|
|
140
|
+
onMessage: (message: T) => void,
|
|
141
|
+
) {
|
|
142
|
+
const connection = new WebSocket(url, 'lime')
|
|
143
|
+
|
|
144
|
+
await new Promise<void>((resolve, reject) => {
|
|
145
|
+
connection.onopen = () => {
|
|
146
|
+
resolve()
|
|
147
|
+
}
|
|
148
|
+
connection.onclose = () => {
|
|
149
|
+
if (!this.closing) {
|
|
150
|
+
this.connectionAttempts++
|
|
151
|
+
if (this.connectionAttempts < 3) {
|
|
152
|
+
this.currentWebSocketPromise = this.connect(url, onConnected, onMessage)
|
|
153
|
+
} else {
|
|
154
|
+
reject(new Error('Failed to connect to WebSocket'))
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
connection.onmessage = (event) => {
|
|
159
|
+
onMessage(JSON.parse(event.data))
|
|
160
|
+
}
|
|
161
|
+
connection.onerror = (error) => {
|
|
162
|
+
if (error instanceof ErrorEvent) {
|
|
163
|
+
throw new Error(`WebSocket error: ${error.message}`, { cause: error })
|
|
164
|
+
} else {
|
|
165
|
+
throw error
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
await onConnected(connection)
|
|
171
|
+
this.connectionAttempts = 0
|
|
172
|
+
|
|
173
|
+
return connection
|
|
174
|
+
}
|
|
175
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { UnknownMessageContent } from './message.ts'
|
|
2
|
+
import type { Identity } from './node.ts'
|
|
3
|
+
|
|
4
|
+
/* Impl ref: https://github.com/takenet/lime-csharp/blob/master/src/Lime.Messaging/Resources/Presence.cs */
|
|
5
|
+
export type Presence = {
|
|
6
|
+
status: 'unavailable' | 'available' | 'busy' | 'away' | 'invisible'
|
|
7
|
+
/**
|
|
8
|
+
* Rule to the server route envelopes addressed to the identity
|
|
9
|
+
* - Instance Routing: Strict match on full node, including instance.
|
|
10
|
+
* - Identity Routing: Matches full node or base identity (no instance).
|
|
11
|
+
* - Promiscuous Identity: Matches base identity or any instance variation.
|
|
12
|
+
*/
|
|
13
|
+
routingRule: 'instance' | 'identity'
|
|
14
|
+
/** The date of the last known presence status for the node */
|
|
15
|
+
lastSeen: string
|
|
16
|
+
/** If true, indicates that the delivery of envelopes for the current session should be distributed by using the round-robin strategy between the resolved routes with this setting */
|
|
17
|
+
roundRobin?: boolean
|
|
18
|
+
/** If true, indicates that the instance should be ignored in the routing rules */
|
|
19
|
+
promiscuous?: boolean
|
|
20
|
+
/** If true, indicates that the current session should receive the messages sent by itself */
|
|
21
|
+
echo?: boolean
|
|
22
|
+
/** The priority of the presence, higher means more priority */
|
|
23
|
+
priority?: number
|
|
24
|
+
/** Present instances for a identity */
|
|
25
|
+
instances: Array<string>
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export type Account = {
|
|
29
|
+
identity: Identity
|
|
30
|
+
fullName: string
|
|
31
|
+
email?: Identity
|
|
32
|
+
phoneNumber?: string
|
|
33
|
+
photoUri?: string
|
|
34
|
+
// When the culture is missing, it should default to pt-BR
|
|
35
|
+
culture?: string
|
|
36
|
+
extras?: Record<string, string | null>
|
|
37
|
+
creationDate: string
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export type BlipLanguage = 'en' | 'pt' | 'es'
|
|
41
|
+
|
|
42
|
+
export type Tenant = {
|
|
43
|
+
canViewMobileDesk: boolean
|
|
44
|
+
creationDate: string
|
|
45
|
+
creationSource: string
|
|
46
|
+
id: string
|
|
47
|
+
isTestContract: boolean
|
|
48
|
+
name: string
|
|
49
|
+
ownerIdentity: string
|
|
50
|
+
paymentAccount?: string
|
|
51
|
+
photoUri?: string
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export type Contact = {
|
|
55
|
+
identity: Identity
|
|
56
|
+
name?: string
|
|
57
|
+
group?: string
|
|
58
|
+
city?: string
|
|
59
|
+
email?: string
|
|
60
|
+
phoneNumber?: string
|
|
61
|
+
source?: string
|
|
62
|
+
taxDocument?: string
|
|
63
|
+
extras?: Record<string, string | null>
|
|
64
|
+
lastMessageDate?: string
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export type ThreadItem = {
|
|
68
|
+
id: string
|
|
69
|
+
direction: 'sent' | 'received'
|
|
70
|
+
date: string
|
|
71
|
+
status: string
|
|
72
|
+
reason?: {
|
|
73
|
+
code: number
|
|
74
|
+
description: string
|
|
75
|
+
}
|
|
76
|
+
metadata?: Record<string, string | null>
|
|
77
|
+
} & UnknownMessageContent
|
|
78
|
+
|
|
79
|
+
export type Comment = {
|
|
80
|
+
id: Identity
|
|
81
|
+
storageDate: string
|
|
82
|
+
authorIdentity: Identity
|
|
83
|
+
content: string
|
|
84
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Identity } from './node.ts'
|
|
2
|
+
|
|
3
|
+
export type EventTrack = {
|
|
4
|
+
ownerIdentity?: Identity
|
|
5
|
+
category: string
|
|
6
|
+
action: string
|
|
7
|
+
extras: Record<string, string>
|
|
8
|
+
contact?: {
|
|
9
|
+
identity: Identity
|
|
10
|
+
}
|
|
11
|
+
messageId?: string
|
|
12
|
+
storageDate?: string
|
|
13
|
+
count?: number
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type NonNullableEventTracking = {
|
|
17
|
+
[P in keyof EventTrack]-?: NonNullable<EventTrack[P]>
|
|
18
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type Plan = {
|
|
2
|
+
name: string
|
|
3
|
+
ownerIdentity: string
|
|
4
|
+
planValue: number
|
|
5
|
+
currencyCode: string
|
|
6
|
+
extras: {
|
|
7
|
+
Cluster: string
|
|
8
|
+
CommandThroughput: string
|
|
9
|
+
DataRetentionPeriod: string
|
|
10
|
+
IdForHubSpotDeal: string
|
|
11
|
+
IsDefaultPlan: string
|
|
12
|
+
MessageThroughput: string
|
|
13
|
+
NotificationThroughput: string
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { Envelope } from './envelope.ts'
|
|
2
|
+
import type { Reason } from './reason.ts'
|
|
3
|
+
|
|
4
|
+
type ResourceCommand<TResource = unknown> = {
|
|
5
|
+
type: string
|
|
6
|
+
resource: TResource
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
type NoResourceCommand = object
|
|
10
|
+
|
|
11
|
+
interface CommandMethodsProperties {
|
|
12
|
+
get: NoResourceCommand
|
|
13
|
+
set: ResourceCommand
|
|
14
|
+
merge: ResourceCommand
|
|
15
|
+
delete: NoResourceCommand | ResourceCommand
|
|
16
|
+
observe: ResourceCommand
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type CommandMethods = keyof CommandMethodsProperties
|
|
20
|
+
|
|
21
|
+
export type Command<TMethod extends CommandMethods> = Envelope & {
|
|
22
|
+
uri: string
|
|
23
|
+
method: TMethod
|
|
24
|
+
} & CommandMethodsProperties[TMethod]
|
|
25
|
+
|
|
26
|
+
export type UnknownCommand = Command<CommandMethods>
|
|
27
|
+
|
|
28
|
+
export type CommandResponse<
|
|
29
|
+
TResponse,
|
|
30
|
+
TMethod extends CommandMethods,
|
|
31
|
+
TStatus extends 'success' | 'failure',
|
|
32
|
+
> = Envelope & {
|
|
33
|
+
method: TMethod
|
|
34
|
+
} & (TStatus extends 'success'
|
|
35
|
+
? {
|
|
36
|
+
status: 'success'
|
|
37
|
+
type?: string
|
|
38
|
+
resource?: TResponse
|
|
39
|
+
}
|
|
40
|
+
: {
|
|
41
|
+
status: 'failure'
|
|
42
|
+
reason: Reason
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
export type UnknownCommandResponse = CommandResponse<unknown, CommandMethods, 'success' | 'failure'>
|
|
46
|
+
|
|
47
|
+
export const isCommand = (envelope: Envelope): envelope is UnknownCommand => 'method' in envelope && 'uri' in envelope
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// This type is weird because typescript doesn't like recursion types with generics
|
|
2
|
+
// Error when done the clean way: Type instantiation is excessively deep and possibly infinite
|
|
3
|
+
export type JsonValue<
|
|
4
|
+
D extends number = 8, // max depth
|
|
5
|
+
A extends Array<0> = [], // accumulator
|
|
6
|
+
> =
|
|
7
|
+
| null
|
|
8
|
+
| boolean
|
|
9
|
+
| number
|
|
10
|
+
| string
|
|
11
|
+
| (A['length'] extends D ? unknown : Array<JsonValue<D, [0, ...A]>>) // arrays
|
|
12
|
+
| (A['length'] extends D ? unknown : { [k: string]: JsonValue<D, [0, ...A]> }) // objects
|
|
13
|
+
|
|
14
|
+
export type JsonObject = Record<string, JsonValue>
|
|
15
|
+
|
|
16
|
+
export type MaybePromise<T> = T | Promise<T>
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export type TicketStatus =
|
|
2
|
+
| 'Waiting'
|
|
3
|
+
| 'Open'
|
|
4
|
+
| 'ClosedAttendant'
|
|
5
|
+
| 'ClosedClient'
|
|
6
|
+
| 'Transferred'
|
|
7
|
+
| 'ClosedClientInactivity'
|
|
8
|
+
| 'ClosedAttendantInactivity'
|
|
9
|
+
|
|
10
|
+
export type AttendanceHour = {
|
|
11
|
+
id: string
|
|
12
|
+
title: string
|
|
13
|
+
description: string
|
|
14
|
+
isMain: boolean
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export enum DayOfWeek {
|
|
18
|
+
Sunday = 'Sunday',
|
|
19
|
+
Monday = 'Monday',
|
|
20
|
+
Tuesday = 'Tuesday',
|
|
21
|
+
Wednesday = 'Wednesday',
|
|
22
|
+
Thursday = 'Thursday',
|
|
23
|
+
Friday = 'Friday',
|
|
24
|
+
Saturday = 'Saturday',
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type AttendanceHourScheduleItem = {
|
|
28
|
+
id: string
|
|
29
|
+
startTime: string
|
|
30
|
+
endTime: string
|
|
31
|
+
dayOfWeek: DayOfWeek
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export type AttendanceHourOffItem = {
|
|
35
|
+
id: string
|
|
36
|
+
reason: string
|
|
37
|
+
startDate: string
|
|
38
|
+
endDate: string
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export type Queue = {
|
|
42
|
+
id: string
|
|
43
|
+
description: string
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export type DetailedAttendanceHour = {
|
|
47
|
+
attendanceHour: AttendanceHour
|
|
48
|
+
attendanceHourScheduleItems: Array<AttendanceHourScheduleItem>
|
|
49
|
+
attendanceHourOffItems: Array<AttendanceHourOffItem>
|
|
50
|
+
queues: Array<Queue>
|
|
51
|
+
}
|