@whitewall/blip-sdk 0.0.185 → 0.0.187
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/dist/cjs/namespaces/namespace.js +2 -0
- package/dist/cjs/namespaces/namespace.js.map +1 -1
- package/dist/cjs/namespaces/portal.js +38 -0
- package/dist/cjs/namespaces/portal.js.map +1 -1
- package/dist/cjs/namespaces/whatsapp.js +29 -0
- package/dist/cjs/namespaces/whatsapp.js.map +1 -1
- package/dist/cjs/sender/gateway/customgatewaysender.js +1 -1
- package/dist/cjs/sender/gateway/customgatewaysender.js.map +1 -1
- package/dist/cjs/sender/http/httpsender.js +2 -2
- package/dist/cjs/sender/http/httpsender.js.map +1 -1
- package/dist/cjs/sender/sender.js +16 -1
- package/dist/cjs/sender/sender.js.map +1 -1
- package/dist/cjs/sender/tcp/tcpsender.js +2 -2
- package/dist/cjs/sender/tcp/tcpsender.js.map +1 -1
- package/dist/cjs/sender/websocket/websocketsender.js +2 -2
- package/dist/cjs/sender/websocket/websocketsender.js.map +1 -1
- package/dist/esm/namespaces/namespace.js +2 -0
- package/dist/esm/namespaces/namespace.js.map +1 -1
- package/dist/esm/namespaces/portal.js +38 -0
- package/dist/esm/namespaces/portal.js.map +1 -1
- package/dist/esm/namespaces/whatsapp.js +29 -0
- package/dist/esm/namespaces/whatsapp.js.map +1 -1
- package/dist/esm/sender/gateway/customgatewaysender.js +1 -1
- package/dist/esm/sender/gateway/customgatewaysender.js.map +1 -1
- package/dist/esm/sender/http/httpsender.js +2 -2
- package/dist/esm/sender/http/httpsender.js.map +1 -1
- package/dist/esm/sender/sender.js +16 -1
- package/dist/esm/sender/sender.js.map +1 -1
- package/dist/esm/sender/tcp/tcpsender.js +2 -2
- package/dist/esm/sender/tcp/tcpsender.js.map +1 -1
- package/dist/esm/sender/websocket/websocketsender.js +2 -2
- package/dist/esm/sender/websocket/websocketsender.js.map +1 -1
- package/dist/types/namespaces/namespace.d.ts +1 -0
- package/dist/types/namespaces/namespace.d.ts.map +1 -1
- package/dist/types/namespaces/portal.d.ts +17 -0
- package/dist/types/namespaces/portal.d.ts.map +1 -1
- package/dist/types/namespaces/whatsapp.d.ts +28 -0
- package/dist/types/namespaces/whatsapp.d.ts.map +1 -1
- package/dist/types/sender/sender.d.ts +5 -1
- package/dist/types/sender/sender.d.ts.map +1 -1
- package/dist/types/types/billing.d.ts +5 -4
- package/dist/types/types/billing.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/namespaces/namespace.ts +3 -0
- package/src/namespaces/portal.ts +60 -0
- package/src/namespaces/whatsapp.ts +60 -0
- package/src/sender/gateway/customgatewaysender.ts +1 -1
- package/src/sender/http/httpsender.ts +2 -2
- package/src/sender/sender.ts +20 -1
- package/src/sender/tcp/tcpsender.ts +2 -2
- package/src/sender/websocket/websocketsender.ts +2 -2
- package/src/types/billing.ts +5 -4
package/src/namespaces/portal.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { BlipClient } from '../client.ts'
|
|
|
2
2
|
import { BlipError } from '../sender/bliperror.ts'
|
|
3
3
|
import { PluginSender } from '../sender/plugin/pluginsender.ts'
|
|
4
4
|
import type { Tenant } from '../types/account.ts'
|
|
5
|
+
import type { Plan } from '../types/billing.ts'
|
|
5
6
|
import { type Identity, Node, type PossiblyNode } from '../types/node.ts'
|
|
6
7
|
import type {
|
|
7
8
|
Application,
|
|
@@ -193,6 +194,27 @@ export class PortalNamespace extends Namespace {
|
|
|
193
194
|
})
|
|
194
195
|
}
|
|
195
196
|
|
|
197
|
+
/**
|
|
198
|
+
* Creates the tenant when it doesn't exist (the caller becomes its owner and admin)
|
|
199
|
+
* or updates it when it does (requires the tenant admin role).
|
|
200
|
+
*/
|
|
201
|
+
public async setTenant(
|
|
202
|
+
tenantId: string,
|
|
203
|
+
tenant: Pick<Tenant, 'name'> &
|
|
204
|
+
Partial<Pick<Tenant, 'photoUri' | 'isTestContract' | 'creationSource' | 'canViewMobileDesk'>>,
|
|
205
|
+
opts?: ConsumeOptions,
|
|
206
|
+
): Promise<void> {
|
|
207
|
+
return await this.sendCommand(
|
|
208
|
+
{
|
|
209
|
+
method: 'set',
|
|
210
|
+
uri: uri`/tenants/${tenantId}`,
|
|
211
|
+
type: 'application/vnd.iris.portal.tenant+json',
|
|
212
|
+
resource: { id: tenantId, ...tenant },
|
|
213
|
+
},
|
|
214
|
+
opts,
|
|
215
|
+
)
|
|
216
|
+
}
|
|
217
|
+
|
|
196
218
|
public async getTenantActiveMessagesMetrics(
|
|
197
219
|
tenantId: string,
|
|
198
220
|
interval: 'D' | 'M' | 'NI',
|
|
@@ -372,6 +394,44 @@ export class PortalNamespace extends Namespace {
|
|
|
372
394
|
)
|
|
373
395
|
}
|
|
374
396
|
|
|
397
|
+
/** Lists all available billing plans. */
|
|
398
|
+
public async getPlans(opts?: ConsumeOptions): Promise<Array<Plan>> {
|
|
399
|
+
return await this.sendCommand(
|
|
400
|
+
{
|
|
401
|
+
method: 'get',
|
|
402
|
+
uri: uri`/plans`,
|
|
403
|
+
},
|
|
404
|
+
{
|
|
405
|
+
collection: true,
|
|
406
|
+
...opts,
|
|
407
|
+
},
|
|
408
|
+
)
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Subscribes a payment account to a plan, replacing the account's active subscription.
|
|
413
|
+
*
|
|
414
|
+
* **Admin-only**: the server rejects any caller other than `admin@blip.ai`
|
|
415
|
+
*
|
|
416
|
+
* The subscriber is the tenant's `paymentAccount` identity, not the tenant id, see {@link getTenant}.
|
|
417
|
+
* Reads such as {@link getTenantSubscription} may briefly return the previous subscription right after this call (eventual consistency).
|
|
418
|
+
*/
|
|
419
|
+
public async setPlanSubscription(
|
|
420
|
+
planId: string,
|
|
421
|
+
paymentAccount: Identity,
|
|
422
|
+
opts?: ConsumeOptions,
|
|
423
|
+
): Promise<TenantSubscription> {
|
|
424
|
+
return await this.sendCommand(
|
|
425
|
+
{
|
|
426
|
+
method: 'set',
|
|
427
|
+
uri: uri`/plans/${planId}/subscriptions`,
|
|
428
|
+
type: 'application/vnd.lime.identity',
|
|
429
|
+
resource: paymentAccount,
|
|
430
|
+
},
|
|
431
|
+
opts,
|
|
432
|
+
)
|
|
433
|
+
}
|
|
434
|
+
|
|
375
435
|
public async deleteTenantUser(tenantId: string, user: Identity, opts?: ConsumeOptions): Promise<void> {
|
|
376
436
|
return await this.sendCommand(
|
|
377
437
|
{
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { BlipClient } from '../client.ts'
|
|
2
|
+
import type { Envelope } from '../types/envelope.ts'
|
|
2
3
|
import type { Identity } from '../types/index.ts'
|
|
3
4
|
import type { MessageTemplate, WhatsAppTemplateLanguage, WhatsappFlow } from '../types/whatsapp.ts'
|
|
4
5
|
import { uri } from '../utils/uri.ts'
|
|
@@ -33,6 +34,65 @@ export class WhatsAppNamespace extends Namespace {
|
|
|
33
34
|
}
|
|
34
35
|
}
|
|
35
36
|
|
|
37
|
+
public getMessageMetadata(envelope: Envelope): {
|
|
38
|
+
/**
|
|
39
|
+
* Business-Scoped User ID of the user (e.g. `BR.11815799212886844830`).
|
|
40
|
+
* Unique per user ↔ business relationship: the same user has different BSUIDs across businesses.
|
|
41
|
+
*/
|
|
42
|
+
bsuid?: string
|
|
43
|
+
/** Parent BSUID, correlating the user across eligible Business Accounts of the same organization */
|
|
44
|
+
parentId?: string
|
|
45
|
+
/** Username of the user, when adopted */
|
|
46
|
+
username?: string
|
|
47
|
+
} {
|
|
48
|
+
return {
|
|
49
|
+
bsuid: envelope.metadata?.['#wa.bsuid'] ?? undefined,
|
|
50
|
+
parentId: envelope.metadata?.['#wa.parentId'] ?? undefined,
|
|
51
|
+
username: envelope.metadata?.['#wa.username'] ?? undefined,
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* This does not query Meta in real time: numbers Blip hasn't seen a BSUID for are simply omitted from the result, which is not an error.
|
|
57
|
+
* Numbers must be in full form (country code + area code + number); the `+` sign is optional.
|
|
58
|
+
*/
|
|
59
|
+
public async getContactsMapping(phoneNumbers: Array<string>, opts?: ConsumeOptions) {
|
|
60
|
+
const results: Array<{
|
|
61
|
+
/** Internal Blip contact id. Phone-based (e.g. `5531999999999`) or a Blip-internal GUID when the phone is not shared */
|
|
62
|
+
id: string
|
|
63
|
+
/** Business-Scoped User ID, unique per user ↔ business relationship (e.g. `BR.11815799212886844830`) */
|
|
64
|
+
bsuid: string
|
|
65
|
+
/** Phone number of the contact when available, without the `+` sign */
|
|
66
|
+
waId?: string
|
|
67
|
+
/** Correlation id between eligible Business Accounts of the same organization */
|
|
68
|
+
parentUserId?: string
|
|
69
|
+
/** Date of the most recent interaction of the contact registered by the platform */
|
|
70
|
+
lastReadDate?: string
|
|
71
|
+
}> = []
|
|
72
|
+
|
|
73
|
+
// The endpoint accepts at most 100 numbers per request and is throughput-limited,
|
|
74
|
+
// so chunks are sent sequentially instead of in parallel
|
|
75
|
+
for (let i = 0; i < phoneNumbers.length; i += 100) {
|
|
76
|
+
const chunk = phoneNumbers.slice(i, i + 100)
|
|
77
|
+
results.push(
|
|
78
|
+
...(await this.sendCommand<'get', typeof results>(
|
|
79
|
+
{
|
|
80
|
+
method: 'get',
|
|
81
|
+
uri: uri`/external-contacts-mapping?${{ phoneNumbers: chunk.join(';') }}`,
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
...opts,
|
|
85
|
+
collection: true,
|
|
86
|
+
// The full list is sent in a single request, there is no pagination
|
|
87
|
+
fetchall: false,
|
|
88
|
+
},
|
|
89
|
+
)),
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return results
|
|
94
|
+
}
|
|
95
|
+
|
|
36
96
|
/**
|
|
37
97
|
* This is a heavily cached endpoint on Meta, so it may take a while to reflect changes
|
|
38
98
|
* @param filters.status - Filter by status of the message template
|
|
@@ -27,7 +27,7 @@ export class CustomGatewayHttpSender extends ConnectionSender implements Sender
|
|
|
27
27
|
'Content-Type': 'application/json',
|
|
28
28
|
Authorization: `Basic ${this.token}`,
|
|
29
29
|
},
|
|
30
|
-
body:
|
|
30
|
+
body: this.serializeOutboundEnvelope(message),
|
|
31
31
|
})
|
|
32
32
|
|
|
33
33
|
if (!response.ok) {
|
|
@@ -52,7 +52,7 @@ export class HttpSender extends ConnectionSender implements Sender {
|
|
|
52
52
|
public sendMessage<Type extends MessageTypes>(message: Message<Type>): Promise<void> {
|
|
53
53
|
return this.withFetchRetryPolicy(async () => {
|
|
54
54
|
await this.throttler.throttle('message')
|
|
55
|
-
const response = await this.fetch('messages',
|
|
55
|
+
const response = await this.fetch('messages', this.serializeOutboundEnvelope(message))
|
|
56
56
|
|
|
57
57
|
if (!response.ok) {
|
|
58
58
|
throw new Error(`Failed to send message: ${response.statusText} (${response.status})`)
|
|
@@ -63,7 +63,7 @@ export class HttpSender extends ConnectionSender implements Sender {
|
|
|
63
63
|
public sendCommand(command: Command<CommandMethods>): Promise<unknown> {
|
|
64
64
|
return this.withFetchRetryPolicy(async () => {
|
|
65
65
|
await this.throttler.throttle('command')
|
|
66
|
-
const response = await this.fetch('commands',
|
|
66
|
+
const response = await this.fetch('commands', this.serializeOutboundEnvelope(command))
|
|
67
67
|
|
|
68
68
|
if (!response.ok) {
|
|
69
69
|
throw new Error(`Failed to send command: ${response.statusText} (${response.status})`)
|
package/src/sender/sender.ts
CHANGED
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
type BlipDomain,
|
|
3
3
|
type Command,
|
|
4
4
|
type CommandMethods,
|
|
5
|
+
type Envelope,
|
|
5
6
|
type Identity,
|
|
6
7
|
type Message,
|
|
7
8
|
type MessageTypes,
|
|
@@ -30,17 +31,30 @@ const ROOT_AUTHORITIES: ReadonlyArray<BlipDomain> = ['msging.net', 'blip.ai', '0
|
|
|
30
31
|
|
|
31
32
|
export class ConnectionSender {
|
|
32
33
|
private readonly _domain: BlipDomain
|
|
34
|
+
private readonly _node: Node
|
|
33
35
|
|
|
34
36
|
constructor(options: ConstructorParameters<ConnectionSenderConstructor>[0]) {
|
|
35
|
-
|
|
37
|
+
this._node = Node.from(options.node)
|
|
38
|
+
const nodeDomain = this._node.domain ?? 'msging.net'
|
|
36
39
|
this._domain =
|
|
37
40
|
ROOT_AUTHORITIES.find((root) => nodeDomain === root || nodeDomain.endsWith(`.${root}`)) ?? 'msging.net'
|
|
38
41
|
}
|
|
39
42
|
|
|
43
|
+
public get node() {
|
|
44
|
+
return this._node
|
|
45
|
+
}
|
|
46
|
+
|
|
40
47
|
public get domain() {
|
|
41
48
|
return this._domain
|
|
42
49
|
}
|
|
43
50
|
|
|
51
|
+
protected serializeOutboundEnvelope(envelope: Envelope): string {
|
|
52
|
+
if (envelope.from && !envelope.pp) {
|
|
53
|
+
envelope.pp = this.node
|
|
54
|
+
}
|
|
55
|
+
return JSON.stringify(envelope)
|
|
56
|
+
}
|
|
57
|
+
|
|
44
58
|
// this method is a mess but provides a good and flexible api with a single method to login
|
|
45
59
|
protected static login<S extends ConnectionSender>(bot: string | Identity, accessKey: string, tenantId?: string): S
|
|
46
60
|
protected static login<S extends ConnectionSender>(token: string, tenantId?: string): S
|
|
@@ -146,6 +160,11 @@ export abstract class OpenConnectionSender extends ConnectionSender implements S
|
|
|
146
160
|
return this.sessionNegotiator?.session ?? null
|
|
147
161
|
}
|
|
148
162
|
|
|
163
|
+
public override get node() {
|
|
164
|
+
// the negotiated local node is authoritative: it carries the server-assigned instance
|
|
165
|
+
return this.sessionNegotiator?.session?.localNode ?? super.node
|
|
166
|
+
}
|
|
167
|
+
|
|
149
168
|
public close(): Promise<void> {
|
|
150
169
|
this.envelopeResolver.close()
|
|
151
170
|
return Promise.resolve()
|
|
@@ -74,7 +74,7 @@ export class TCPSender extends OpenConnectionSender {
|
|
|
74
74
|
const socket = await this.connectionHandle.get()
|
|
75
75
|
await this.sessionNegotiator?.ensurePresence()
|
|
76
76
|
|
|
77
|
-
socket.write(
|
|
77
|
+
socket.write(this.serializeOutboundEnvelope(message))
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
public sendCommand(command: Command<CommandMethods>): Promise<unknown> {
|
|
@@ -86,7 +86,7 @@ export class TCPSender extends OpenConnectionSender {
|
|
|
86
86
|
const socket = await this.connectionHandle.get()
|
|
87
87
|
await this.sessionNegotiator?.ensurePresence(command.uri)
|
|
88
88
|
|
|
89
|
-
socket.write(
|
|
89
|
+
socket.write(this.serializeOutboundEnvelope(command))
|
|
90
90
|
|
|
91
91
|
const response = (await envelopeResponsePromise) as CommandResponse<
|
|
92
92
|
unknown,
|
|
@@ -56,7 +56,7 @@ export class WebSocketSender extends OpenConnectionSender {
|
|
|
56
56
|
const webSocket = await this.connectionHandle.get()
|
|
57
57
|
await this.sessionNegotiator?.ensurePresence()
|
|
58
58
|
|
|
59
|
-
webSocket.send(
|
|
59
|
+
webSocket.send(this.serializeOutboundEnvelope(message))
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
public sendCommand(command: Command<CommandMethods>): Promise<unknown> {
|
|
@@ -67,7 +67,7 @@ export class WebSocketSender extends OpenConnectionSender {
|
|
|
67
67
|
|
|
68
68
|
const webSocket = await this.connectionHandle.get()
|
|
69
69
|
await this.sessionNegotiator?.ensurePresence(command.uri)
|
|
70
|
-
webSocket.send(
|
|
70
|
+
webSocket.send(this.serializeOutboundEnvelope(command))
|
|
71
71
|
|
|
72
72
|
const response = (await envelopeResponsePromise) as CommandResponse<
|
|
73
73
|
unknown,
|
package/src/types/billing.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
export type Plan = {
|
|
2
|
+
id: string
|
|
2
3
|
name: string
|
|
3
4
|
ownerIdentity: string
|
|
4
|
-
planValue
|
|
5
|
-
currencyCode
|
|
6
|
-
extras: {
|
|
5
|
+
planValue?: number
|
|
6
|
+
currencyCode?: string
|
|
7
|
+
extras: Partial<{
|
|
7
8
|
Cluster: string
|
|
8
9
|
CommandThroughput: string
|
|
9
10
|
DataRetentionPeriod: string
|
|
@@ -11,5 +12,5 @@ export type Plan = {
|
|
|
11
12
|
IsDefaultPlan: string
|
|
12
13
|
MessageThroughput: string
|
|
13
14
|
NotificationThroughput: string
|
|
14
|
-
}
|
|
15
|
+
}>
|
|
15
16
|
}
|