@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.
Files changed (50) hide show
  1. package/package.json +2 -2
  2. package/src/client.ts +117 -0
  3. package/src/index.ts +6 -0
  4. package/src/namespaces/account.ts +729 -0
  5. package/src/namespaces/activecampaign.ts +285 -0
  6. package/src/namespaces/analytics.ts +230 -0
  7. package/src/namespaces/billing.ts +17 -0
  8. package/src/namespaces/builder.ts +52 -0
  9. package/src/namespaces/configurations.ts +19 -0
  10. package/src/namespaces/context.ts +67 -0
  11. package/src/namespaces/desk.ts +679 -0
  12. package/src/namespaces/media.ts +39 -0
  13. package/src/namespaces/namespace.ts +125 -0
  14. package/src/namespaces/plugins.ts +223 -0
  15. package/src/namespaces/portal.ts +402 -0
  16. package/src/namespaces/scheduler.ts +88 -0
  17. package/src/namespaces/whatsapp.ts +383 -0
  18. package/src/sender/bliperror.ts +42 -0
  19. package/src/sender/enveloperesolver.ts +148 -0
  20. package/src/sender/gateway/customgatewaysender.ts +43 -0
  21. package/src/sender/http/httpsender.ts +94 -0
  22. package/src/sender/index.ts +7 -0
  23. package/src/sender/plugin/communication.ts +72 -0
  24. package/src/sender/plugin/pluginsender.ts +75 -0
  25. package/src/sender/security.ts +33 -0
  26. package/src/sender/sender.ts +145 -0
  27. package/src/sender/sessionnegotiator.ts +175 -0
  28. package/src/sender/tcp/tcpsender.ts +252 -0
  29. package/src/sender/throttler.ts +36 -0
  30. package/src/sender/websocket/websocketsender.ts +175 -0
  31. package/src/types/account.ts +84 -0
  32. package/src/types/analytics.ts +18 -0
  33. package/src/types/billing.ts +15 -0
  34. package/src/types/command.ts +47 -0
  35. package/src/types/commons.ts +16 -0
  36. package/src/types/desk.ts +51 -0
  37. package/src/types/envelope.ts +9 -0
  38. package/src/types/flow.ts +327 -0
  39. package/src/types/index.ts +13 -0
  40. package/src/types/message.ts +116 -0
  41. package/src/types/node.ts +86 -0
  42. package/src/types/notification.ts +18 -0
  43. package/src/types/plugins.ts +51 -0
  44. package/src/types/portal.ts +39 -0
  45. package/src/types/reason.ts +22 -0
  46. package/src/types/session.ts +22 -0
  47. package/src/types/whatsapp.ts +84 -0
  48. package/src/utils/odata.ts +114 -0
  49. package/src/utils/random.ts +3 -0
  50. 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
+ }