@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,729 @@
1
+ import type { BlipClient } from '../client.ts'
2
+ import { BlipError } from '../sender/bliperror.ts'
3
+ import type {
4
+ Account,
5
+ ApplicationFlow,
6
+ BuilderConfiguration,
7
+ BuilderFlow,
8
+ BuilderLatestPublication,
9
+ BuilderLatestPublications,
10
+ Comment,
11
+ Contact,
12
+ HistoryIndex,
13
+ Identity,
14
+ Presence,
15
+ Publication,
16
+ State,
17
+ ThreadItem,
18
+ } from '../types/index.ts'
19
+ import type { Notification } from '../types/notification.ts'
20
+ import type { ODataFilter } from '../utils/odata.ts'
21
+ import { uri } from '../utils/uri.ts'
22
+ import { type ConsumeOptions, Namespace, type SendCommandOptions } from './namespace.ts'
23
+
24
+ export class AccountNamespace extends Namespace {
25
+ constructor(blipClient: BlipClient, defaultOptions?: SendCommandOptions) {
26
+ super(blipClient, '', defaultOptions)
27
+ }
28
+
29
+ public async ping(opts?: ConsumeOptions): Promise<void> {
30
+ return await this.sendCommand(
31
+ {
32
+ method: 'get',
33
+ uri: uri`/ping`,
34
+ },
35
+ opts,
36
+ )
37
+ }
38
+
39
+ public async getPresence(opts?: ConsumeOptions): Promise<Presence> {
40
+ return await this.sendCommand(
41
+ {
42
+ method: 'get',
43
+ uri: uri`/presence`,
44
+ },
45
+ opts,
46
+ )
47
+ }
48
+
49
+ public async setPresence(presence: Omit<Presence, 'lastSeen' | 'instances'>, opts?: ConsumeOptions): Promise<void> {
50
+ return await this.sendCommand(
51
+ {
52
+ method: 'set',
53
+ uri: uri`/presence`,
54
+ type: 'application/vnd.lime.presence+json',
55
+ resource: presence,
56
+ },
57
+ opts,
58
+ )
59
+ }
60
+
61
+ public async me(opts?: ConsumeOptions): Promise<Account> {
62
+ return await this.sendCommand(
63
+ {
64
+ method: 'get',
65
+ uri: uri`/account`,
66
+ },
67
+ opts,
68
+ )
69
+ }
70
+
71
+ public async getAccount(account: string, opts?: ConsumeOptions): Promise<Account> {
72
+ return await this.sendCommand(
73
+ {
74
+ method: 'get',
75
+ uri: uri`/accounts/${account}`,
76
+ },
77
+ opts,
78
+ )
79
+ }
80
+
81
+ public async setAccount(
82
+ account: Partial<Omit<Account, 'identity'>> & { password?: string },
83
+ opts?: ConsumeOptions,
84
+ ): Promise<void> {
85
+ return await this.sendCommand(
86
+ {
87
+ method: 'set',
88
+ uri: uri`/account`,
89
+ type: 'application/vnd.lime.account+json',
90
+ resource: account,
91
+ },
92
+ opts,
93
+ )
94
+ }
95
+
96
+ public async getContexts(contact: Identity, opts?: ConsumeOptions): Promise<Array<string>> {
97
+ try {
98
+ return await this.sendCommand(
99
+ {
100
+ method: 'get',
101
+ uri: uri`/contexts/${contact}`,
102
+ },
103
+ {
104
+ collection: true,
105
+ ...opts,
106
+ },
107
+ )
108
+ } catch (err) {
109
+ if (err instanceof BlipError && err.code === 67) {
110
+ return []
111
+ }
112
+
113
+ throw err
114
+ }
115
+ }
116
+
117
+ public async getContextsWithValues(
118
+ contact: Identity,
119
+ opts?: ConsumeOptions,
120
+ ): Promise<Array<{ name: string; value: string }>> {
121
+ try {
122
+ return await this.sendCommand(
123
+ {
124
+ method: 'get',
125
+ uri: uri`/contexts/${contact}?withContextValues=true`,
126
+ },
127
+ {
128
+ collection: true,
129
+ ...opts,
130
+ },
131
+ )
132
+ } catch (err) {
133
+ if (
134
+ err instanceof BlipError &&
135
+ (err.code === 67 ||
136
+ // This is a workaround for a bug in Blip where it returns an error when there are no contexts
137
+ (err.code === 61 && err.message.includes('Object reference not set to an instance of an object.')))
138
+ ) {
139
+ return []
140
+ }
141
+
142
+ throw err
143
+ }
144
+ }
145
+
146
+ public async getContext(contact: Identity, key: string, opts?: ConsumeOptions): Promise<string | undefined> {
147
+ try {
148
+ return await this.sendCommand(
149
+ {
150
+ method: 'get',
151
+ uri: uri`/contexts/${contact}/${key}`,
152
+ },
153
+ opts,
154
+ )
155
+ } catch (err) {
156
+ if (err instanceof BlipError && err.code === 67) {
157
+ return undefined
158
+ }
159
+
160
+ throw err
161
+ }
162
+ }
163
+
164
+ public async setContext(
165
+ contact: Identity,
166
+ key: string,
167
+ value: string,
168
+ ttlSeconds?: number,
169
+ opts?: ConsumeOptions,
170
+ ): Promise<void> {
171
+ return await this.sendCommand(
172
+ {
173
+ method: 'set',
174
+ uri: uri`/contexts/${contact}/${key}?${{
175
+ expiration: ttlSeconds ? ttlSeconds * 1000 : undefined,
176
+ }}`,
177
+ type: 'text/plain',
178
+ resource: value,
179
+ },
180
+ opts,
181
+ )
182
+ }
183
+
184
+ public async deleteContext(contact: Identity, key: string, opts?: ConsumeOptions): Promise<void> {
185
+ return await this.sendCommand(
186
+ {
187
+ method: 'delete',
188
+ uri: uri`/contexts/${contact}/${key}`,
189
+ },
190
+ opts,
191
+ )
192
+ }
193
+
194
+ public async getContacts(filter?: ODataFilter<Contact>, opts?: ConsumeOptions): Promise<Array<Contact>> {
195
+ return await this.sendCommand(
196
+ {
197
+ method: 'get',
198
+ uri: uri`/contacts?${{ $filter: filter?.toString() }}`,
199
+ },
200
+ {
201
+ collection: true,
202
+ ...opts,
203
+ },
204
+ )
205
+ }
206
+
207
+ public async getContactsByIdentity(identities: Array<Identity>, opts?: ConsumeOptions): Promise<Array<Contact>> {
208
+ return await this.sendCommand(
209
+ {
210
+ method: 'get',
211
+ uri: uri`/contacts?${{ identities: identities.join(';') }}`,
212
+ },
213
+ {
214
+ collection: true,
215
+ ...opts,
216
+ },
217
+ )
218
+ }
219
+
220
+ public async getContactComments(contact: Identity, opts?: ConsumeOptions): Promise<Array<Comment>> {
221
+ try {
222
+ return await this.sendCommand(
223
+ {
224
+ method: 'get',
225
+ uri: uri`/contacts/${contact}/comments`,
226
+ },
227
+ {
228
+ collection: true,
229
+ ...opts,
230
+ },
231
+ )
232
+ } catch (err) {
233
+ if (err instanceof BlipError && err.code === 67) {
234
+ return []
235
+ }
236
+
237
+ throw err
238
+ }
239
+ }
240
+
241
+ public async getContact(contact: Identity, opts?: ConsumeOptions): Promise<Contact | undefined> {
242
+ try {
243
+ return await this.sendCommand(
244
+ {
245
+ method: 'get',
246
+ uri: uri`/contacts/${contact}`,
247
+ },
248
+ opts,
249
+ )
250
+ } catch (err) {
251
+ if (err instanceof BlipError && err.code === 67) {
252
+ return undefined
253
+ }
254
+
255
+ throw err
256
+ }
257
+ }
258
+
259
+ public async setContact(contact: Contact, opts?: ConsumeOptions): Promise<void> {
260
+ return await this.sendCommand(
261
+ {
262
+ method: 'set',
263
+ uri: uri`/contacts`,
264
+ type: 'application/vnd.lime.contact+json',
265
+ resource: contact,
266
+ },
267
+ opts,
268
+ )
269
+ }
270
+
271
+ public async mergeContact(
272
+ contact: Partial<Contact> & Pick<Contact, 'identity'>,
273
+ opts?: ConsumeOptions,
274
+ ): Promise<void> {
275
+ return await this.sendCommand(
276
+ {
277
+ method: 'merge',
278
+ uri: uri`/contacts`,
279
+ type: 'application/vnd.lime.contact+json',
280
+ resource: contact,
281
+ },
282
+ opts,
283
+ )
284
+ }
285
+
286
+ public async deleteContact(contact: Identity, opts?: ConsumeOptions): Promise<void> {
287
+ return await this.sendCommand(
288
+ {
289
+ method: 'delete',
290
+ uri: uri`/contacts/${contact}`,
291
+ },
292
+ opts,
293
+ )
294
+ }
295
+
296
+ public async getBuckets(opts?: ConsumeOptions): Promise<Array<string>> {
297
+ return await this.sendCommand(
298
+ {
299
+ method: 'get',
300
+ uri: uri`/buckets`,
301
+ },
302
+ {
303
+ collection: true,
304
+ ...opts,
305
+ },
306
+ )
307
+ }
308
+
309
+ public async getBucket<T = string>(key: string, opts?: ConsumeOptions): Promise<T | undefined> {
310
+ try {
311
+ return await this.sendCommand(
312
+ {
313
+ method: 'get',
314
+ uri: uri`/buckets/${key}`,
315
+ },
316
+ opts,
317
+ )
318
+ } catch (err) {
319
+ if (err instanceof BlipError && err.code === 67) {
320
+ return undefined
321
+ }
322
+
323
+ throw err
324
+ }
325
+ }
326
+
327
+ public async setBucket<T = string>(
328
+ key: string,
329
+ value: T,
330
+ ttlSeconds?: number,
331
+ opts?: ConsumeOptions,
332
+ ): Promise<void> {
333
+ return await this.sendCommand(
334
+ {
335
+ method: 'set',
336
+ uri: uri`/buckets/${key}?${{
337
+ expiration: ttlSeconds ? ttlSeconds * 1000 : undefined,
338
+ }}`,
339
+ type: typeof value === 'string' ? 'text/plain' : 'application/json',
340
+ resource: value,
341
+ },
342
+ opts,
343
+ )
344
+ }
345
+
346
+ public async deleteBucket(key: string, opts?: ConsumeOptions): Promise<void> {
347
+ return await this.sendCommand(
348
+ {
349
+ method: 'delete',
350
+ uri: uri`/buckets/${key}`,
351
+ },
352
+ opts,
353
+ )
354
+ }
355
+
356
+ public async getCallerConfigurations(
357
+ opts?: ConsumeOptions,
358
+ ): Promise<Array<{ owner: Identity; caller: Identity; name: string; value: string }>> {
359
+ return await this.sendCommand(
360
+ {
361
+ method: 'get',
362
+ uri: uri`/configuration/caller`,
363
+ },
364
+ {
365
+ collection: true,
366
+ ...opts,
367
+ },
368
+ )
369
+ }
370
+
371
+ public async getConfigurations(opts?: ConsumeOptions): Promise<Record<string, string>> {
372
+ try {
373
+ return await this.sendCommand(
374
+ {
375
+ method: 'get',
376
+ uri: uri`/configuration`,
377
+ },
378
+ opts,
379
+ )
380
+ } catch (err) {
381
+ if (err instanceof BlipError && err.code === 67) {
382
+ return {}
383
+ }
384
+
385
+ throw err
386
+ }
387
+ }
388
+
389
+ /**
390
+ * The caller will be the authenticated bot, and the owner can be set on opts.ownerIdentity
391
+ */
392
+ public async setConfigurations(configuration: Record<string, string>, opts?: ConsumeOptions) {
393
+ return await this.sendCommand(
394
+ {
395
+ method: 'set',
396
+ type: 'application/json',
397
+ uri: uri`/configuration`,
398
+ resource: configuration,
399
+ },
400
+ opts,
401
+ )
402
+ }
403
+
404
+ public async getThreads(startDate: Date | string, endDate: Date | string, opts: Omit<ConsumeOptions, 'skip'> = {}) {
405
+ const start = new Date(startDate)
406
+ const end = new Date(endDate)
407
+ if (start > end) return []
408
+
409
+ const ONE_WEEK_MS = 7 * 24 * 60 * 60 * 1000
410
+ const parallel = opts.optimistic && opts.fetchall && end.getTime() - start.getTime() > ONE_WEEK_MS
411
+
412
+ const segments: Array<{ start: Date; end: Date }> = []
413
+ if (parallel) {
414
+ let segEnd = end
415
+ while (segEnd >= start) {
416
+ const segStart = new Date(Math.max(start.getTime(), segEnd.getTime() - ONE_WEEK_MS))
417
+ segments.push({ start: segStart, end: segEnd })
418
+ segEnd = new Date(segStart.getTime() - 1)
419
+ }
420
+ } else {
421
+ segments.push({ start, end })
422
+ }
423
+
424
+ const seenIdentities = new Set<string>()
425
+ let threads: Array<{ ownerIdentity: Identity; identity: Identity; lastMessage: ThreadItem }> = []
426
+ await Promise.all(
427
+ segments.map(async ({ start: segStart, end: segEnd }) => {
428
+ let cursor = new Date(segEnd)
429
+
430
+ while (cursor >= segStart) {
431
+ const take = Math.min(opts.take ?? 100, opts.max ?? Number.POSITIVE_INFINITY)
432
+ if (take <= 0) break
433
+
434
+ const items = await this.sendCommand<'get', typeof threads>(
435
+ { method: 'get', uri: uri`/threads?${{ messageDate: cursor }}` },
436
+ { collection: true, ...opts, take, fetchall: false, optimistic: false },
437
+ )
438
+
439
+ for (const thread of items) {
440
+ if (new Date(thread.lastMessage.date) >= start && !seenIdentities.has(thread.identity)) {
441
+ seenIdentities.add(thread.identity)
442
+ threads.push(thread)
443
+ }
444
+ }
445
+
446
+ if (items.length < take || !opts.fetchall) break
447
+ cursor = new Date(items[items.length - 1].lastMessage.date)
448
+ }
449
+ }),
450
+ )
451
+
452
+ if (opts.max) {
453
+ threads = threads.slice(0, opts.max)
454
+ }
455
+
456
+ return threads.toSorted((a, b) => a.lastMessage.date.localeCompare(b.lastMessage.date))
457
+ }
458
+
459
+ /**
460
+ * @param query.messageId The message ID to start the thread from
461
+ * @param query.referenceDate A reference date in ISO 8601 format YYYY-MM-DD to fetch the thread from
462
+ * @param query.referenceDateDirection The direction to fetch the thread from the reference date (inclusive if after). Can be 'before' or 'after'
463
+ * @returns The thread items in descending order
464
+ */
465
+ public async getThread(
466
+ contact: Identity,
467
+ query?: {
468
+ messageId?: string
469
+ referenceDate?: string | Date
470
+ referenceDateDirection?: 'before' | 'after'
471
+ refreshExpiredMedia?: boolean
472
+ },
473
+ opts?: Omit<ConsumeOptions, 'skip'>,
474
+ ): Promise<Array<ThreadItem>> {
475
+ let thread: Array<ThreadItem> = []
476
+ let lastMessage = query?.messageId
477
+ let lastStorageDate = query?.referenceDate ? new Date(query.referenceDate).toISOString() : undefined
478
+ let hasMore = true
479
+ do {
480
+ const take = Math.min(opts?.take ?? 100, opts?.max ? opts.max - thread.length : Number.POSITIVE_INFINITY)
481
+ if (take <= 0) {
482
+ break
483
+ }
484
+
485
+ const threadItems = await this.sendCommand<'get', Array<ThreadItem>>(
486
+ {
487
+ method: 'get',
488
+ // Always prefer /threads-merged over /threads, because it handle additional whatsapp numbers automatically
489
+ // For example, in Brazil the same number can work with or without a leading 9
490
+ uri: uri`/threads-merged/${contact}?${{
491
+ direction: query?.referenceDateDirection === 'after' ? 'asc' : 'desc',
492
+ refreshExpiredMedia: query?.refreshExpiredMedia,
493
+ messageId: lastMessage,
494
+ storageDate: lastStorageDate,
495
+ // Tunnels have empty threads (at least with router context enabled)
496
+ // This is needed to fetch the entire thread from the originator
497
+ // Can contain messages from other subbots
498
+ getFromOriginator: true,
499
+ }}`,
500
+ },
501
+ {
502
+ collection: true,
503
+ ...opts,
504
+ take,
505
+ fetchall: false,
506
+ },
507
+ )
508
+ thread = [...thread, ...threadItems]
509
+
510
+ hasMore = threadItems.length === take
511
+ lastMessage = threadItems[threadItems.length - 1]?.id
512
+ lastStorageDate = threadItems[threadItems.length - 1]?.date
513
+ } while (hasMore && opts?.fetchall)
514
+
515
+ return thread.toSorted((a, b) => b.date.localeCompare(a.date))
516
+ }
517
+
518
+ public async getNotifications(
519
+ filter?: {
520
+ message?: string
521
+ },
522
+ opts?: ConsumeOptions,
523
+ ): Promise<Array<Notification>> {
524
+ return await this.sendCommand(
525
+ {
526
+ method: 'get',
527
+ uri: uri`/notifications?${{ id: filter?.message }}`,
528
+ },
529
+ {
530
+ collection: true,
531
+ ...opts,
532
+ },
533
+ )
534
+ }
535
+
536
+ public async getTemplateType(opts?: ConsumeOptions): Promise<'master' | 'builder'> {
537
+ const configuration = await this.getCallerConfigurations(opts)
538
+ const template = configuration.find((config) => config.name === 'Template')?.value
539
+ if (template === 'master') {
540
+ return 'master'
541
+ } else if (template === 'builder') {
542
+ return 'builder'
543
+ } else {
544
+ const account = await this.me(opts)
545
+ if (account.extras?.template) {
546
+ return account.extras.template as 'master' | 'builder'
547
+ } else {
548
+ throw new Error('Could not determine template type')
549
+ }
550
+ }
551
+ }
552
+
553
+ public async getChildren(
554
+ opts?: ConsumeOptions,
555
+ ): Promise<Array<{ identifier: string; fullName: string; service: string }>> {
556
+ const configuration = await this.getCallerConfigurations(opts)
557
+ const template = configuration.find((config) => config.name === 'Template')?.value
558
+
559
+ if (template === 'master') {
560
+ const application = configuration.find((config) => config.name === 'Application')?.value
561
+ if (application) {
562
+ try {
563
+ const children = JSON.parse(application)?.settings?.children
564
+ if (children) {
565
+ return children.map(
566
+ (child: {
567
+ shortName: string
568
+ longName: string
569
+ service: string
570
+ }) => ({
571
+ identifier: child.shortName,
572
+ fullName: child.longName,
573
+ service: child.service,
574
+ }),
575
+ )
576
+ }
577
+ } catch (err) {
578
+ throw new Error(`Failed to parse children configuration: ${err}`)
579
+ }
580
+ }
581
+ }
582
+
583
+ return []
584
+ }
585
+
586
+ public async getBuilderFlow(state: 'published' | 'working', opts?: ConsumeOptions) {
587
+ switch (state) {
588
+ case 'published':
589
+ return await this.getBucket<BuilderFlow>('blip_portal:builder_published_flow', opts)
590
+ case 'working':
591
+ return await this.getBucket<BuilderFlow>('blip_portal:builder_working_flow', opts)
592
+ default:
593
+ throw new Error('Invalid state. Use "published" or "working".')
594
+ }
595
+ }
596
+
597
+ public async getBuilderGlobalActions(state: 'published' | 'working', opts?: ConsumeOptions) {
598
+ switch (state) {
599
+ case 'published':
600
+ return await this.getBucket<State>('blip_portal:builder_published_global_actions', opts)
601
+ case 'working':
602
+ return await this.getBucket<State>('blip_portal:builder_working_global_actions', opts)
603
+ default:
604
+ throw new Error('Invalid state. Use "published" or "working".')
605
+ }
606
+ }
607
+
608
+ public async getBuilderConfiguration(state: 'published' | 'working', opts?: ConsumeOptions) {
609
+ switch (state) {
610
+ case 'published':
611
+ return await this.getBucket<BuilderConfiguration>('blip_portal:builder_published_configuration', opts)
612
+ case 'working':
613
+ return await this.getBucket<BuilderConfiguration>('blip_portal:builder_working_configuration', opts)
614
+ default:
615
+ throw new Error('Invalid state. Use "published" or "working".')
616
+ }
617
+ }
618
+
619
+ public async setBuilderFlow(state: 'published' | 'working', value: BuilderFlow, opts?: ConsumeOptions) {
620
+ switch (state) {
621
+ case 'published':
622
+ return await this.setBucket('blip_portal:builder_published_flow', value, undefined, opts)
623
+ case 'working':
624
+ return await this.setBucket('blip_portal:builder_working_flow', value, undefined, opts)
625
+ default:
626
+ throw new Error('Invalid state. Use "published" or "working".')
627
+ }
628
+ }
629
+
630
+ public async setBuilderGlobalActions(state: 'published' | 'working', value: State, opts?: ConsumeOptions) {
631
+ switch (state) {
632
+ case 'published':
633
+ return await this.setBucket('blip_portal:builder_published_global_actions', value, undefined, opts)
634
+ case 'working':
635
+ return await this.setBucket('blip_portal:builder_working_global_actions', value, undefined, opts)
636
+ default:
637
+ throw new Error('Invalid state. Use "published" or "working".')
638
+ }
639
+ }
640
+
641
+ public async setBuilderConfiguration(
642
+ state: 'published' | 'working',
643
+ value: BuilderConfiguration,
644
+ opts?: ConsumeOptions,
645
+ ) {
646
+ switch (state) {
647
+ case 'published':
648
+ return await this.setBucket('blip_portal:builder_published_configuration', value, undefined, opts)
649
+ case 'working':
650
+ return await this.setBucket('blip_portal:builder_working_configuration', value, undefined, opts)
651
+ default:
652
+ throw new Error('Invalid state. Use "published" or "working".')
653
+ }
654
+ }
655
+
656
+ public async getBuilderLatestPublications(opts?: ConsumeOptions) {
657
+ return await this.getBucket<BuilderLatestPublications>('blip_portal:builder_latestpublications', opts)
658
+ }
659
+
660
+ public async getBuilderLatestPublicationByIndex(index: HistoryIndex, opts?: ConsumeOptions) {
661
+ return await this.getBucket<BuilderLatestPublication>(`blip_portal:builder_latestpublications:${index}`, opts)
662
+ }
663
+
664
+ public async setBuilderLatestPublications(
665
+ publication: Publication,
666
+ value: BuilderLatestPublication,
667
+ opts?: ConsumeOptions,
668
+ ) {
669
+ const latestPublications = await this.getBuilderLatestPublications(opts)
670
+
671
+ if (!latestPublications) {
672
+ throw new Error('Latest publications not found')
673
+ }
674
+
675
+ const indexMax = 10
676
+
677
+ const newIndex = (
678
+ latestPublications.lastInsertedIndex === indexMax ? 1 : latestPublications.lastInsertedIndex + 1
679
+ ) as HistoryIndex
680
+ const newPublication = {
681
+ ...publication,
682
+ publishedAt: new Date().toISOString(),
683
+ index: newIndex,
684
+ }
685
+
686
+ if (latestPublications.publications.length >= indexMax) {
687
+ latestPublications.publications = latestPublications.publications.slice(0, -1)
688
+ }
689
+
690
+ latestPublications.publications = [newPublication, ...latestPublications.publications]
691
+ latestPublications.lastInsertedIndex = newIndex
692
+
693
+ await Promise.all([
694
+ this.setBucket(`blip_portal:builder_latestpublications:${newIndex}`, value, undefined, opts),
695
+ this.setBucket('blip_portal:builder_latestpublications', latestPublications, undefined, opts),
696
+ ])
697
+ }
698
+
699
+ public async getApplicationPublishedFlowConfiguration(opts?: ConsumeOptions) {
700
+ const configurations = await this.getCallerConfigurations(opts)
701
+
702
+ const application = configurations.find((c) => c.name === 'Application' && !c.owner.startsWith('construction.'))
703
+
704
+ if (application?.value) {
705
+ return JSON.parse(application.value) as ApplicationFlow
706
+ }
707
+
708
+ throw new Error('Application flow not found value')
709
+ }
710
+
711
+ public async setApplicationPublishedFlowConfiguration(value: ApplicationFlow, opts?: ConsumeOptions) {
712
+ const configurations = await this.getCallerConfigurations(opts)
713
+
714
+ const application = configurations.find((c) => c.name === 'Application' && !c.owner.startsWith('construction.'))
715
+
716
+ if (application) {
717
+ return await this.setConfigurations(
718
+ {
719
+ [application.name]: JSON.stringify(value),
720
+ },
721
+ {
722
+ ownerIdentity: application.owner,
723
+ },
724
+ )
725
+ }
726
+
727
+ throw new Error('Application flow not found')
728
+ }
729
+ }