@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,285 @@
1
+ import type { BlipClient } from '../client.ts'
2
+ import { type Identity, Node } from '../types/node.ts'
3
+ import { uri } from '../utils/uri.ts'
4
+ import { type ConsumeOptions, Namespace, type SendCommandOptions } from './namespace.ts'
5
+
6
+ type AudienceSummary = {
7
+ id: string
8
+ name: string
9
+ campaignStatus: string
10
+ failed: number
11
+ processed: number
12
+ read: number
13
+ received: number
14
+ total: number
15
+ scheduled?: string
16
+ created: string
17
+ }
18
+
19
+ export class ActiveCampaignNamespace extends Namespace {
20
+ constructor(blipClient: BlipClient, defaultOptions?: SendCommandOptions) {
21
+ super(blipClient, 'activecampaign', defaultOptions)
22
+ }
23
+
24
+ public createAndDispatchBatchCampaign(
25
+ campaign: {
26
+ name: string
27
+ flowId: string
28
+ stateId: string
29
+ campaignSender: string
30
+ subbotIdentity?: Identity
31
+ agentEmail?: string
32
+ scheduled?: string
33
+ tags?: Array<string>
34
+ },
35
+ audiences: Array<{
36
+ recipient: string
37
+ buttonVariable?: string
38
+ mediaVariable?: string
39
+ bodyVariables: Array<string>
40
+ additionalContactExtras?: Record<string, string | null>
41
+ }>,
42
+ message: {
43
+ template: string
44
+ language: string
45
+ },
46
+ opts?: ConsumeOptions,
47
+ ): Promise<{
48
+ id: string
49
+ status: string
50
+ created: string
51
+ }> {
52
+ const messageVariables = [
53
+ audiences[0].mediaVariable ? 'urllink' : undefined,
54
+ audiences[0].buttonVariable ? 'button' : undefined,
55
+ ...audiences[0].bodyVariables.map((_, i) => `variable${i + 1}`),
56
+ ].filter((v) => v)
57
+ return this.sendCommand(
58
+ {
59
+ method: 'set',
60
+ uri: uri`/campaign/full`,
61
+ type: 'application/vnd.iris.activecampaign.full-campaign+json',
62
+ resource: {
63
+ campaign: {
64
+ name: campaign.name,
65
+ campaignType: 'BATCH',
66
+ flowId: campaign.flowId,
67
+ stateId: campaign.stateId,
68
+ campaignSender: campaign.campaignSender,
69
+ masterstate: campaign.subbotIdentity,
70
+ attendanceRedirect: campaign.agentEmail ? new Node(campaign.agentEmail, 'blip.ai') : undefined,
71
+ scheduled: campaign.scheduled,
72
+ tags: campaign.tags,
73
+ },
74
+ audiences: audiences.map((audience) => {
75
+ return {
76
+ recipient: audience.recipient,
77
+ messageParams: {
78
+ urllink: audience.mediaVariable,
79
+ button: audience.buttonVariable,
80
+ ...Object.fromEntries(audience.bodyVariables.map((v, i) => [`variable${i + 1}`, v])),
81
+ ...audience.additionalContactExtras,
82
+ },
83
+ }
84
+ }),
85
+ message: {
86
+ messageTemplate: message.template,
87
+ messageTemplateLanguage: message.language,
88
+ // Needed because of a bug where the API interprets empty array as a single empty variable
89
+ // E.g. "messageParams": [""]
90
+ messageParams: messageVariables.length > 0 ? messageVariables : undefined,
91
+ },
92
+ },
93
+ },
94
+ opts,
95
+ )
96
+ }
97
+
98
+ public async cancelCampaign(campaign: string, opts?: ConsumeOptions) {
99
+ return await this.sendCommand(
100
+ {
101
+ method: 'delete',
102
+ uri: uri`/campaigns/${campaign}`,
103
+ },
104
+ opts,
105
+ )
106
+ }
107
+
108
+ public async getCampaigns(opts?: ConsumeOptions) {
109
+ // This is needed because this route throws an error if passed an $skip or $take query parameter
110
+ const resource = await this.sendCommand<'get', { items: Array<{ id: string; name: string }> }>(
111
+ {
112
+ method: 'get',
113
+ uri: uri`/campaigns`,
114
+ },
115
+ opts,
116
+ )
117
+ return resource.items
118
+ }
119
+
120
+ public getCampaign(
121
+ campaign: string,
122
+ opts?: ConsumeOptions,
123
+ ): Promise<{
124
+ id: string
125
+ name: string
126
+ campaignType: 'BATCH' | 'INDIVIDUAL'
127
+ masterState: Identity
128
+ flowId: string
129
+ stateId: string
130
+ campaignSender: Identity
131
+ status: string
132
+ created: string
133
+ }> {
134
+ return this.sendCommand(
135
+ {
136
+ method: 'get',
137
+ uri: uri`/campaigns/${campaign}`,
138
+ },
139
+ opts,
140
+ )
141
+ }
142
+
143
+ public async getCampaignMessage(campaign: string, opts?: ConsumeOptions) {
144
+ const [message] = await this.sendCommand<
145
+ 'get',
146
+ Array<{
147
+ channelType: 'WHATSAPP'
148
+ messageTemplate: string
149
+ messageTemplateLanguage: string
150
+ messageParams: Array<string>
151
+ }>
152
+ >(
153
+ {
154
+ method: 'get',
155
+ uri: uri`/messages/${campaign}`,
156
+ },
157
+ {
158
+ collection: true,
159
+ ...opts,
160
+ },
161
+ )
162
+
163
+ return message
164
+ }
165
+
166
+ public getAudienceSummaries(
167
+ filter?: {
168
+ scheduled?: boolean
169
+ campaignName?: string
170
+ },
171
+ opts?: ConsumeOptions,
172
+ ): Promise<Array<AudienceSummary>> {
173
+ return this.sendCommand(
174
+ {
175
+ method: 'get',
176
+ uri: uri`/audience-summary?${filter}`,
177
+ },
178
+ {
179
+ collection: true,
180
+ ...opts,
181
+ },
182
+ )
183
+ }
184
+
185
+ public async getAudienceSummary(campaign: string, opts?: ConsumeOptions) {
186
+ const [summary] = await this.sendCommand<'get', Array<AudienceSummary>>(
187
+ {
188
+ method: 'get',
189
+ uri: uri`/audience-summary/${campaign}`,
190
+ },
191
+ {
192
+ collection: true,
193
+ ...opts,
194
+ },
195
+ )
196
+
197
+ return summary
198
+ }
199
+
200
+ public getAudience(
201
+ campaign: string,
202
+ opts?: ConsumeOptions,
203
+ ): Promise<
204
+ Array<{
205
+ OwnerIdentity: Identity
206
+ CampaignId: string
207
+ recipient: string
208
+ recipientType: 'PhoneNumber'
209
+ channelType: 'WHATSAPP'
210
+ messageParams: Record<string, string>
211
+ status: string
212
+ validatedAccount: Identity
213
+ // These strings are actually timestamps
214
+ // Can be used to infer the message's state
215
+ processed?: string
216
+ received?: string
217
+ read?: string
218
+ failed?: string
219
+ reasonCode?: number
220
+ reasonDescription?: string
221
+ }>
222
+ > {
223
+ return this.sendCommand(
224
+ {
225
+ method: 'get',
226
+ uri: uri`/audiences/${campaign}`,
227
+ },
228
+ {
229
+ collection: true,
230
+ take: 1000,
231
+ ...opts,
232
+ },
233
+ )
234
+ }
235
+
236
+ public getCampaignsSummaries(
237
+ filters: {
238
+ createdDate: string | Date
239
+ source?: 'Portal' | 'Desk' | 'API'
240
+ campaignSender?: string
241
+ },
242
+ opts?: ConsumeOptions,
243
+ ): Promise<
244
+ Array<{
245
+ id: string
246
+ name: string
247
+ messageTemplate: string
248
+ masterState: Identity
249
+ flowId: string
250
+ stateId: string
251
+ attendanceRedirect: Identity
252
+ campaignSender: Identity
253
+ sendDate: string
254
+ statusAudience: Array<{
255
+ recipientIdentity: Identity
256
+ status: 'NOT_PROCESSED' | 'PROCESSED' | 'RECEIVED' | 'READ' | 'FAILED'
257
+ processed?: string
258
+ received?: string
259
+ read?: string
260
+ failed?: string
261
+ reasonCode?: number
262
+ reasonDescription?: string
263
+ numberStatus: string
264
+ }>
265
+ }>
266
+ > {
267
+ return this.sendCommand(
268
+ {
269
+ method: 'get',
270
+ uri: uri`/campaigns/summaries?${{
271
+ created:
272
+ typeof filters.createdDate === 'string'
273
+ ? filters.createdDate
274
+ : filters.createdDate.toISOString(),
275
+ SourceApplication: filters.source,
276
+ CampaignSender: filters.campaignSender,
277
+ }}`,
278
+ },
279
+ {
280
+ collection: true,
281
+ ...opts,
282
+ },
283
+ )
284
+ }
285
+ }
@@ -0,0 +1,230 @@
1
+ import type { BlipClient } from '../client.ts'
2
+ import type { EventTrack, NonNullableEventTracking } from '../types/analytics.ts'
3
+ import type { Identity } from '../types/node.ts'
4
+ import { uri } from '../utils/uri.ts'
5
+ import { type ConsumeOptions, Namespace, type SendCommandOptions } from './namespace.ts'
6
+
7
+ export class AnalyticsNamespace extends Namespace {
8
+ constructor(blipClient: BlipClient, defaultOptions?: SendCommandOptions) {
9
+ super(blipClient, 'analytics', defaultOptions)
10
+ }
11
+
12
+ public async track(
13
+ contact: Identity | undefined,
14
+ category: string,
15
+ action: string,
16
+ extras: Record<string, string | undefined> = {},
17
+ opts?: ConsumeOptions,
18
+ ): Promise<void> {
19
+ return await this.sendCommand(
20
+ {
21
+ method: 'observe',
22
+ uri: uri`/event-track`,
23
+ type: 'application/vnd.iris.eventTrack+json',
24
+ resource: {
25
+ category,
26
+ action,
27
+ contact: contact
28
+ ? {
29
+ identity: contact,
30
+ }
31
+ : undefined,
32
+ extras: {
33
+ ...(contact ? { contactIdentity: contact } : {}),
34
+ ...extras,
35
+ },
36
+ } satisfies EventTrack,
37
+ },
38
+ opts,
39
+ )
40
+ }
41
+
42
+ public async getCategories(
43
+ filter?: string,
44
+ opts?: ConsumeOptions,
45
+ ): Promise<Array<Pick<NonNullableEventTracking, 'category'>>> {
46
+ return await this.sendCommand(
47
+ {
48
+ method: 'get',
49
+ uri: uri`/event-track?${{ categoryFilter: filter }}`,
50
+ },
51
+ {
52
+ collection: true,
53
+ ...opts,
54
+ },
55
+ )
56
+ }
57
+
58
+ /** @returns Quantity of actions in the category between the start and end date */
59
+ public async getCategoryCount(
60
+ category: string,
61
+ startDate: Date,
62
+ endDate: Date,
63
+ opts?: ConsumeOptions,
64
+ ): Promise<number> {
65
+ const { count } = await this.sendCommand<'get', { count: number }>(
66
+ {
67
+ method: 'get',
68
+ uri: uri`/event-track-count/${category}?${{ startDate, endDate }}`,
69
+ },
70
+ opts,
71
+ )
72
+ return count
73
+ }
74
+
75
+ /** @returns Counts of each action in the category grouped by day between the start and end date */
76
+ public async getCategoryActions(
77
+ category: string,
78
+ startDate: Date,
79
+ endDate: Date,
80
+ opts?: ConsumeOptions,
81
+ ): Promise<Array<Pick<NonNullableEventTracking, 'storageDate' | 'category' | 'action' | 'count'>>> {
82
+ return await this.sendCommand(
83
+ {
84
+ method: 'get',
85
+ uri: uri`/event-track/${category}?${{ startDate, endDate }}`,
86
+ },
87
+ {
88
+ collection: true,
89
+ ...opts,
90
+ },
91
+ )
92
+ }
93
+
94
+ public async getTrackings(category: string, action: string, startDate: Date, endDate: Date, opts?: ConsumeOptions) {
95
+ const trackings = await this.sendCommand<
96
+ 'get',
97
+ Array<
98
+ Pick<EventTrack, 'ownerIdentity' | 'storageDate' | 'category' | 'action' | 'extras' | 'contact'> & {
99
+ contact: {
100
+ // This is a workaround, sometimes Blip returns it on pascal case
101
+ Identity: Identity
102
+ }
103
+ }
104
+ >
105
+ >(
106
+ {
107
+ method: 'get',
108
+ uri: uri`/event-track/${category}/${action}?${{ startDate, endDate }}`,
109
+ },
110
+ {
111
+ collection: true,
112
+ ...opts,
113
+ },
114
+ )
115
+
116
+ return trackings.map((tracking) => ({
117
+ ...tracking,
118
+ contact: {
119
+ identity: tracking.contact?.Identity ?? tracking.contact?.identity ?? tracking.extras.contactIdentity,
120
+ },
121
+ }))
122
+ }
123
+
124
+ public async hasWebhook(url: string, opts?: ConsumeOptions) {
125
+ const webhooks = await this.getWebhooks(opts)
126
+ return webhooks.some((webhook) => webhook.url === url)
127
+ }
128
+
129
+ public async getWebhooks(opts?: ConsumeOptions) {
130
+ const configurations = await this.blipClient.account.getConfigurations({
131
+ ...opts,
132
+ ownerIdentity: this.identity,
133
+ })
134
+
135
+ const webhookUrls = configurations['Webhook.Url']?.split(';') ?? []
136
+ const advancedWebhookSettings = JSON.parse(configurations['Webhook.AdvancedSettings'] ?? '[]')
137
+
138
+ type Settings = {
139
+ settings: {
140
+ dispatchTypes: Array<'contacts' | 'messages' | 'eventtrackings'>
141
+ customHeaders: Record<string, string>
142
+ }
143
+ }
144
+
145
+ return webhookUrls
146
+ .filter((url) => url)
147
+ .map((url) => {
148
+ const settings = advancedWebhookSettings.find(
149
+ (webhookSettings: Record<string, Settings>) => webhookSettings[url]?.settings,
150
+ )?.[url]?.settings
151
+
152
+ return {
153
+ url,
154
+ settings: (settings ?? {
155
+ dispatchTypes: ['contacts', 'messages', 'eventtrackings'],
156
+ customHeaders: {},
157
+ }) as Settings['settings'],
158
+ }
159
+ })
160
+ }
161
+
162
+ /**
163
+ * It will add the url as an active webhook if it doesn't exist yet
164
+ * If the url already exists, it will update the settings
165
+ * @param url The url to set
166
+ * @param settings.listeners Default: ['contacts', 'messages', 'eventtrackings']
167
+ */
168
+ public async setWebhook(
169
+ url: string,
170
+ settings?: {
171
+ listeners?: Array<'contacts' | 'messages' | 'eventtrackings'>
172
+ headers?: Record<string, string>
173
+ },
174
+ opts?: ConsumeOptions,
175
+ ) {
176
+ const configurations = await this.blipClient.account.getConfigurations({
177
+ ...opts,
178
+ ownerIdentity: this.identity,
179
+ })
180
+
181
+ let webhookUrls = configurations['Webhook.Url']?.split(';') ?? []
182
+ if (!webhookUrls.includes(url)) {
183
+ webhookUrls = [...webhookUrls, url]
184
+ }
185
+
186
+ const webhookSettings = {
187
+ dispatchTypes: settings?.listeners ?? ['contacts', 'messages', 'eventtrackings'],
188
+ customHeaders: settings?.headers ?? {},
189
+ }
190
+
191
+ const advancedWebhookSettings = JSON.parse(configurations['Webhook.AdvancedSettings'] ?? '[]').filter(
192
+ (urls: Record<string, never>) => !urls[url],
193
+ )
194
+
195
+ await this.blipClient.account.setConfigurations(
196
+ {
197
+ 'Webhook.Url': webhookUrls.join(';'),
198
+ 'Webhook.IsValid': 'True',
199
+ 'Webhook.AdvancedSettings': JSON.stringify([
200
+ ...advancedWebhookSettings,
201
+ {
202
+ [url]: {
203
+ settings: webhookSettings,
204
+ },
205
+ },
206
+ ]),
207
+ },
208
+ { ...opts, ownerIdentity: this.identity },
209
+ )
210
+ }
211
+
212
+ public async deleteWebhook(url: string, opts?: ConsumeOptions) {
213
+ const configurations = await this.blipClient.account.getConfigurations({
214
+ ...opts,
215
+ ownerIdentity: this.identity,
216
+ })
217
+
218
+ const currentWebhookUrls = configurations['Webhook.Url']?.split(';') ?? []
219
+ const advancedWebhookSettings = JSON.parse(configurations['Webhook.AdvancedSettings'] ?? '[]')
220
+ await this.blipClient.account.setConfigurations(
221
+ {
222
+ 'Webhook.Url': currentWebhookUrls.filter((u) => u !== url).join(';'),
223
+ 'Webhook.AdvancedSettings': JSON.stringify(
224
+ advancedWebhookSettings.filter((urls: Record<string, never>) => !urls[url]),
225
+ ),
226
+ },
227
+ { ...opts, ownerIdentity: this.identity },
228
+ )
229
+ }
230
+ }
@@ -0,0 +1,17 @@
1
+ import type { BlipClient } from '../client.ts'
2
+ import type { Plan } from '../types/billing.ts'
3
+ import { uri } from '../utils/uri.ts'
4
+ import { Namespace, type SendCommandOptions } from './namespace.ts'
5
+
6
+ export class BillingNamespace extends Namespace {
7
+ constructor(blipClient: BlipClient, defaultOptions?: SendCommandOptions) {
8
+ super(blipClient, 'billing', { ...defaultOptions, domain: 'blip.ai' })
9
+ }
10
+
11
+ public async getPlan(tenantId: string): Promise<Plan> {
12
+ return await this.sendCommand({
13
+ method: 'get',
14
+ uri: uri`/tenants/${tenantId}/subscription/plan`,
15
+ })
16
+ }
17
+ }
@@ -0,0 +1,52 @@
1
+ import type { BlipClient } from '../client.ts'
2
+ import { BlipError } from '../sender/bliperror.ts'
3
+ import { uri } from '../utils/uri.ts'
4
+ import { type ConsumeOptions, Namespace, type SendCommandOptions } from './namespace.ts'
5
+
6
+ export class BuilderNamespace extends Namespace {
7
+ constructor(blipClient: BlipClient, defaultOptions?: SendCommandOptions) {
8
+ super(blipClient, 'builder', defaultOptions)
9
+ }
10
+
11
+ public async getFlowId(botIdentifier?: string, opts?: ConsumeOptions): Promise<string | undefined> {
12
+ try {
13
+ return await this.sendCommand(
14
+ {
15
+ method: 'get',
16
+ uri: uri`/flow-id?${{ shortName: botIdentifier }}`,
17
+ },
18
+ opts,
19
+ )
20
+ } catch (err) {
21
+ if (err instanceof BlipError && err.code === 67) {
22
+ return undefined
23
+ }
24
+
25
+ throw err
26
+ }
27
+ }
28
+
29
+ public async getFlowStates(
30
+ botIdentifier?: string,
31
+ opts?: ConsumeOptions,
32
+ ): Promise<Array<{ id: string; name: string }>> {
33
+ try {
34
+ return await this.sendCommand(
35
+ {
36
+ method: 'get',
37
+ uri: uri`/flow/states?${{ shortName: botIdentifier }}`,
38
+ },
39
+ {
40
+ collection: true,
41
+ ...opts,
42
+ },
43
+ )
44
+ } catch (err) {
45
+ if (err instanceof BlipError && err.code === 67) {
46
+ return []
47
+ }
48
+
49
+ throw err
50
+ }
51
+ }
52
+ }
@@ -0,0 +1,19 @@
1
+ import type { BlipClient } from '../client.ts'
2
+ import { uri } from '../utils/uri.ts'
3
+ import { Namespace, type SendCommandOptions } from './namespace.ts'
4
+
5
+ export class ConfigurationsNamespace extends Namespace {
6
+ constructor(blipClient: BlipClient, defaultOptions?: SendCommandOptions) {
7
+ super(blipClient, 'configurations', defaultOptions)
8
+ }
9
+
10
+ public getConfigurations(opts?: SendCommandOptions): Promise<Record<string, string>> {
11
+ return this.sendCommand(
12
+ {
13
+ method: 'get',
14
+ uri: uri`/configuration`,
15
+ },
16
+ opts,
17
+ )
18
+ }
19
+ }
@@ -0,0 +1,67 @@
1
+ import type { BlipClient, Ticket } from '../index.ts'
2
+ import { PluginSender } from '../sender/plugin/pluginsender.ts'
3
+ import type { Account, BlipLanguage } from '../types/account.ts'
4
+ import { Namespace, type SendCommandOptions } from './namespace.ts'
5
+
6
+ export class ContextNamespace extends Namespace {
7
+ constructor(blipClient: BlipClient, defaultOptions?: SendCommandOptions) {
8
+ super(blipClient, '', defaultOptions)
9
+ }
10
+
11
+ public async getCurrentTicket(): Promise<Ticket> {
12
+ const sender = this.checkSenderRealm('desk')
13
+ const { item } = await sender.channel.post<{ item: Ticket }>('currentTicket', null)
14
+ return item
15
+ }
16
+
17
+ public getUser(): Promise<Account> {
18
+ const sender = this.checkSenderRealm('portal')
19
+ return sender.channel.post('getUserContext', { command: 'getCurrentUser' })
20
+ }
21
+
22
+ public getLanguage(): Promise<BlipLanguage> {
23
+ const sender = this.checkSenderRealm()
24
+ return sender.channel.post('getCurrentLanguage', null)
25
+ }
26
+
27
+ public toast(type: 'success' | 'warning' | 'danger', message: string): void {
28
+ const sender = this.checkSenderRealm()
29
+ const fixedType = this.isOnBlipDesk() && type === 'danger' ? 'error' : type
30
+ sender.channel.post('toast', { type: fixedType, message }, { fireAndForget: true })
31
+ }
32
+
33
+ public changeHeight(height: number): void {
34
+ const sender = this.checkSenderRealm('portal')
35
+ sender.channel.post('heightChange', height, { fireAndForget: true })
36
+ }
37
+
38
+ public loading(show: boolean): void {
39
+ const sender = this.checkSenderRealm()
40
+ if (show) {
41
+ sender.channel.post('startLoading', {}, { fireAndForget: true })
42
+ } else {
43
+ sender.channel.post('stopLoading', {}, { fireAndForget: true })
44
+ }
45
+ }
46
+
47
+ public getUserToken(): Promise<string> {
48
+ const sender = this.checkSenderRealm('portal')
49
+ return sender.channel.post('getToken', null)
50
+ }
51
+
52
+ public isOnBlipDesk(): boolean {
53
+ return PluginSender.getRealm(this.blipClient) === 'desk'
54
+ }
55
+
56
+ public isOnPortal(): boolean {
57
+ return PluginSender.getRealm(this.blipClient) === 'portal'
58
+ }
59
+
60
+ private checkSenderRealm(realm?: 'portal' | 'desk') {
61
+ if (realm && PluginSender.getRealm(this.blipClient) !== realm) {
62
+ throw new Error(`This command is only allowed for ${realm} realm`)
63
+ }
64
+
65
+ return PluginSender.check(this.blipClient)
66
+ }
67
+ }