Skip to content

Commit 1733e23

Browse files
committed
sdk: 2.0.0-beta.6 - batch sends for unicasts
1 parent 8e96907 commit 1733e23

File tree

9 files changed

+126
-30
lines changed

9 files changed

+126
-30
lines changed

packages/sdk/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@dialectlabs/sdk",
3-
"version": "2.0.0-beta.5",
3+
"version": "2.0.0-beta.6",
44
"type": "module",
55
"repository": "[email protected]:dialectlabs/sdk.git",
66
"author": "dialectlabs",

packages/sdk/src/dapp/dapp.interface.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ export interface DappAddresses {
5353

5454
export interface DappMessages {
5555
send(command: SendDappMessageCommand): Promise<void>;
56+
57+
sendBatch(commands: UnicastDappMessageCommand[]): Promise<void>;
5658
}
5759

5860
export interface CreateDappCommand {

packages/sdk/src/dialect-cloud-api/data-service-errors.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
IllegalArgumentError,
77
ResourceAlreadyExistsError,
88
ResourceNotFoundError,
9+
TooManyRequestsError,
910
UnknownError,
1011
} from '../sdk/errors';
1112
import { DialectCloudUnreachableError } from '../internal/errors';
@@ -38,6 +39,9 @@ export async function withErrorParsing<T>(
3839
if (e.statusCode === 409) {
3940
throw onResourceAlreadyExists(e);
4041
}
42+
if (e.statusCode === 429) {
43+
throw new TooManyRequestsError(createMessage(e));
44+
}
4145
if (e.statusCode === 422) {
4246
throw new BusinessConstraintViolationError(createMessage(e));
4347
}

packages/sdk/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export * from './messaging/messaging.interface';
2020
export * from './messaging/errors';
2121
export * from './internal/messaging/data-service-messaging';
2222
export * from './utils/bytes-utils';
23+
export * from './utils/collection-utils';
2324
export * from './sdk/errors';
2425
export * from './sdk/sdk.interface';
2526
export * from './sdk/constants';

packages/sdk/src/internal/dapp/alerts-v2-dapp-messages.ts

Lines changed: 56 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
type SendAlertRequest,
1717
} from '../../dialect-cloud-api/v2/application-api';
1818
import { withErrorParsing } from '../../dialect-cloud-api/data-service-errors';
19+
import { chunk } from '../../utils/collection-utils';
1920

2021
export const AddressTypeToChannel = {
2122
[AddressType.Email]: Channel.Email,
@@ -43,6 +44,59 @@ export class AlertsV2DappMessages implements DappMessages {
4344
return this.broadcast(command);
4445
}
4546

47+
async sendBatch(commands: UnicastDappMessageCommand[]): Promise<void> {
48+
if (commands.length === 0) {
49+
return;
50+
}
51+
52+
// Convert commands to SendAlertRequest using map and filter, applying same business logic as send method
53+
const alerts = commands
54+
.filter((command) => command.addressTypes?.length !== 0)
55+
.map((command) => this.unicastCommandToAlert(command));
56+
57+
// If no valid alerts after filtering, return early
58+
if (alerts.length === 0) {
59+
return;
60+
}
61+
62+
// Split into chunks of max 500 items
63+
const chunks = chunk(alerts, 500);
64+
65+
// Send chunks sequentially
66+
for (const alertChunk of chunks) {
67+
try {
68+
await withErrorParsing(
69+
this.api.sendBatch(this.dappId, {
70+
alerts: alertChunk,
71+
}),
72+
);
73+
} catch (e) {
74+
console.error(
75+
`Error during sending batch dapp messages: ${JSON.stringify(e)}`,
76+
);
77+
}
78+
}
79+
}
80+
81+
private unicastCommandToAlert(
82+
command: UnicastDappMessageCommand,
83+
): SendAlertRequest {
84+
return {
85+
recipient: {
86+
type: 'subscriber',
87+
walletAddress: command.recipient.toString(),
88+
},
89+
channels: AlertsV2DappMessages.mapChannels(command.addressTypes),
90+
topicId: command.notificationTypeId,
91+
message: {
92+
title: command.title,
93+
body: command.message,
94+
image: command.imageUrl,
95+
actions: AlertsV2DappMessages.mapActions(command.actionsV2),
96+
},
97+
};
98+
}
99+
46100
static mapChannels(addressTypes?: AddressType[]) {
47101
if (!addressTypes) {
48102
return [Channel.InApp, Channel.Email, Channel.Telegram];
@@ -73,22 +127,8 @@ export class AlertsV2DappMessages implements DappMessages {
73127
}
74128

75129
private async unicast(command: UnicastDappMessageCommand) {
76-
await withErrorParsing(
77-
this.api.sendAlert(this.dappId, {
78-
recipient: {
79-
type: 'subscriber',
80-
walletAddress: command.recipient.toString(),
81-
},
82-
channels: AlertsV2DappMessages.mapChannels(command.addressTypes),
83-
topicId: command.notificationTypeId,
84-
message: {
85-
title: command.title,
86-
body: command.message,
87-
image: command.imageUrl,
88-
actions: AlertsV2DappMessages.mapActions(command.actionsV2),
89-
},
90-
}),
91-
);
130+
const alertRequest = this.unicastCommandToAlert(command);
131+
await withErrorParsing(this.api.sendAlert(this.dappId, alertRequest));
92132
}
93133

94134
private async multicast(command: MulticastDappMessageCommand) {

packages/sdk/src/internal/dapp/dapp-messages-facade.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type {
22
DappMessages,
33
SendDappMessageCommand,
4+
UnicastDappMessageCommand,
45
} from '../../dapp/dapp.interface';
56
import { DialectSdkError, IllegalArgumentError } from '../../sdk/errors';
67

@@ -37,4 +38,29 @@ export class DappMessagesFacade implements DappMessages {
3738
throw error;
3839
}
3940
}
41+
42+
async sendBatch(commands: UnicastDappMessageCommand[]): Promise<void> {
43+
const allSettled = await Promise.allSettled(
44+
this.dappMessageBackends.map((it) => it.sendBatch(commands)),
45+
);
46+
const errors = allSettled
47+
.filter((it) => it.status === 'rejected')
48+
.map((it) => it as PromiseRejectedResult)
49+
.map((it) => it.reason as DialectSdkError);
50+
if (errors.length > 0) {
51+
console.error(
52+
`Error during sending batch dapp messages: ${errors.map((it) =>
53+
JSON.stringify(it),
54+
)}`,
55+
);
56+
}
57+
const fulfilled = allSettled.filter((it) => it.status === 'fulfilled');
58+
if (errors.length > 0 && fulfilled.length === 0) {
59+
const error: DialectSdkError = {
60+
...errors[0]!,
61+
details: errors,
62+
};
63+
throw error;
64+
}
65+
}
4066
}

packages/sdk/src/internal/dapp/data-service-dapp-messages.ts

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -41,22 +41,27 @@ export class DataServiceDappMessages implements DappMessages {
4141
this.addressTypePredicate,
4242
);
4343

44-
if ('recipient' in command) {
45-
return this.unicast({
46-
...command,
47-
addressTypes: addressTypesFiltered,
48-
});
49-
}
50-
if ('recipients' in command) {
51-
return this.multicast({
52-
...command,
53-
addressTypes: addressTypesFiltered,
54-
});
44+
if (addressTypesFiltered.length === 0) {
45+
return;
5546
}
56-
return this.broadcast({
47+
48+
const commandWithAddressTypesFiltered = {
5749
...command,
5850
addressTypes: addressTypesFiltered,
59-
});
51+
};
52+
53+
if ('recipient' in commandWithAddressTypesFiltered) {
54+
return this.unicast(commandWithAddressTypesFiltered);
55+
}
56+
if ('recipients' in commandWithAddressTypesFiltered) {
57+
return this.multicast(commandWithAddressTypesFiltered);
58+
}
59+
return this.broadcast(commandWithAddressTypesFiltered);
60+
}
61+
62+
async sendBatch(commands: UnicastDappMessageCommand[]): Promise<void> {
63+
console.warn('DataServiceDappMessages.sendBatch - not supported');
64+
return;
6065
}
6166

6267
private async unicast(command: UnicastDappMessageCommand) {

packages/sdk/src/sdk/errors.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,4 +72,10 @@ export class ResourceNotFoundError extends DialectSdkError {
7272
}
7373
}
7474

75+
export class TooManyRequestsError extends DialectSdkError {
76+
constructor(msg?: string) {
77+
super(TooManyRequestsError.name, 'Error', msg);
78+
}
79+
}
80+
7581
export abstract class IdentityError extends DialectSdkError {}

packages/sdk/src/utils/collection-utils.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,15 @@ export function groupBy<T, K extends keyof any>(arr: T[], key: (i: T) => K) {
44
return groups;
55
}, {} as Record<K, T[]>);
66
}
7+
8+
export function chunk<T>(array: T[], size: number): T[][] {
9+
if (size <= 0) {
10+
throw new Error('Chunk size must be greater than 0');
11+
}
12+
13+
const chunks: T[][] = [];
14+
for (let i = 0; i < array.length; i += size) {
15+
chunks.push(array.slice(i, i + size));
16+
}
17+
return chunks;
18+
}

0 commit comments

Comments
 (0)