@airhornjs/azure 5.0.1
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/LICENSE +21 -0
- package/README.md +254 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +253 -0
- package/package.json +63 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Jared Wray
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+

|
|
2
|
+
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
[](https://github.com/jaredwray/airhorn/actions/workflows/tests.yml)
|
|
6
|
+
[](https://codecov.io/gh/jaredwray/airhorn)
|
|
7
|
+
[](https://github.com/jaredwray/airhorn/blob/master/LICENSE)
|
|
8
|
+
[](https://npmjs.com/package/@airhorn/azure)
|
|
9
|
+
[](https://npmjs.com/package/@airhorn/azure)
|
|
10
|
+
|
|
11
|
+
# @airhorn/azure
|
|
12
|
+
|
|
13
|
+
Azure Communication Services and Notification Hubs provider for Airhorn.
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install airhorn @airhorn/azure
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Features
|
|
22
|
+
|
|
23
|
+
- SMS sending via Azure Communication Services
|
|
24
|
+
- Email sending via Azure Communication Services
|
|
25
|
+
- Mobile push notifications to iOS and Android devices via Azure Notification Hubs
|
|
26
|
+
- Automatic error handling and retry support
|
|
27
|
+
- Integration with Airhorn's notification system
|
|
28
|
+
- Support for multiple connection strings
|
|
29
|
+
|
|
30
|
+
## Usage
|
|
31
|
+
|
|
32
|
+
### SMS with Azure Communication Services
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
import { Airhorn } from 'airhorn';
|
|
36
|
+
import { AirhornAzure } from '@airhorn/azure';
|
|
37
|
+
|
|
38
|
+
// Create Azure provider for SMS
|
|
39
|
+
const azureProvider = new AirhornAzure({
|
|
40
|
+
connectionString: 'endpoint=https://your-service.communication.azure.com/;accesskey=your-key',
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// Create Airhorn instance with Azure provider
|
|
44
|
+
const airhorn = new Airhorn({
|
|
45
|
+
providers: [azureProvider],
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const data = {
|
|
49
|
+
orderId: '12345',
|
|
50
|
+
customerName: 'John',
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// Send SMS
|
|
54
|
+
const template = {
|
|
55
|
+
from: '+1234567890', // Your sender phone number (must be provisioned in Azure)
|
|
56
|
+
content: 'Hello <%= customerName %>!, your order #<%= orderId %> has been shipped!',
|
|
57
|
+
type: AirhornSendType.SMS,
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const result = await airhorn.send(
|
|
61
|
+
'+0987654321', // to
|
|
62
|
+
template,
|
|
63
|
+
data,
|
|
64
|
+
AirhornSendType.SMS
|
|
65
|
+
);
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Email with Azure Communication Services
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
import { Airhorn } from 'airhorn';
|
|
72
|
+
import { AirhornAzure } from '@airhorn/azure';
|
|
73
|
+
|
|
74
|
+
// Create Azure provider with email support
|
|
75
|
+
const azureProvider = new AirhornAzure({
|
|
76
|
+
connectionString: 'endpoint=https://your-service.communication.azure.com/;accesskey=your-key',
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// Create Airhorn instance
|
|
80
|
+
const airhorn = new Airhorn({
|
|
81
|
+
providers: [azureProvider],
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const data = {
|
|
85
|
+
orderId: '656565',
|
|
86
|
+
customerName: 'John',
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
// Send Email
|
|
90
|
+
const template = {
|
|
91
|
+
from: 'DoNotReply@yourdomain.com', // Must be verified domain in Azure
|
|
92
|
+
subject: 'Order Confirmation: <%= orderId %>',
|
|
93
|
+
content: 'Hi <%= customerName %>, your order #<%= orderId %> has been confirmed!',
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const result = await airhorn.send(
|
|
97
|
+
'recipient@example.com', // to
|
|
98
|
+
template,
|
|
99
|
+
data,
|
|
100
|
+
AirhornSendType.Email
|
|
101
|
+
);
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Mobile Push Notifications with Azure Notification Hubs
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
import { Airhorn } from 'airhorn';
|
|
108
|
+
import { AirhornAzure } from '@airhorn/azure';
|
|
109
|
+
|
|
110
|
+
const azureProvider = new AirhornAzure({
|
|
111
|
+
notificationHubConnectionString: 'Endpoint=sb://your-namespace.servicebus.windows.net/;SharedAccessKeyName=DefaultFullSharedAccessSignature;SharedAccessKey=your-key',
|
|
112
|
+
notificationHubName: 'your-hub-name',
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const airhorn = new Airhorn({
|
|
116
|
+
providers: [azureProvider],
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
const data = {
|
|
120
|
+
orderId: '12345',
|
|
121
|
+
customerName: 'John',
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
// Send to iOS device via APNs
|
|
125
|
+
const template = {
|
|
126
|
+
from: 'YourApp',
|
|
127
|
+
content: JSON.stringify({
|
|
128
|
+
aps: {
|
|
129
|
+
alert: {
|
|
130
|
+
title: 'New Order',
|
|
131
|
+
body: 'Hi <%= customerName %>You have a new order #<%= orderId %>',
|
|
132
|
+
},
|
|
133
|
+
badge: 1,
|
|
134
|
+
sound: 'default',
|
|
135
|
+
},
|
|
136
|
+
// Custom data
|
|
137
|
+
orderId: '12345',
|
|
138
|
+
}),
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
// Send using tag expression
|
|
142
|
+
await airhorn.send(
|
|
143
|
+
'user:john-doe', // Tag expression
|
|
144
|
+
template,
|
|
145
|
+
data,
|
|
146
|
+
AirhornSendType.MobilePush,
|
|
147
|
+
{
|
|
148
|
+
platform: 'apple',
|
|
149
|
+
tags: 'user:john-doe && ios',
|
|
150
|
+
},
|
|
151
|
+
);
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Custom Capabilities
|
|
155
|
+
|
|
156
|
+
You can specify which services to enable using the `capabilities` option:
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
// SMS only
|
|
160
|
+
const smsProvider = new AirhornAzure({
|
|
161
|
+
connectionString: 'your-connection-string',
|
|
162
|
+
capabilities: [AirhornSendType.SMS],
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// Email only
|
|
166
|
+
const emailProvider = new AirhornAzure({
|
|
167
|
+
connectionString: 'your-connection-string',
|
|
168
|
+
capabilities: [AirhornSendType.Email],
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// Mobile Push only
|
|
172
|
+
const pushProvider = new AirhornAzure({
|
|
173
|
+
notificationHubConnectionString: 'your-hub-connection-string',
|
|
174
|
+
notificationHubName: 'your-hub-name',
|
|
175
|
+
capabilities: [AirhornSendType.MobilePush],
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
// All capabilities (explicit)
|
|
179
|
+
const allProvider = new AirhornAzure({
|
|
180
|
+
connectionString: 'your-communication-services-connection-string',
|
|
181
|
+
notificationHubConnectionString: 'your-hub-connection-string',
|
|
182
|
+
notificationHubName: 'your-hub-name',
|
|
183
|
+
capabilities: [AirhornSendType.SMS, AirhornSendType.Email, AirhornSendType.MobilePush],
|
|
184
|
+
});
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## Configuration
|
|
188
|
+
|
|
189
|
+
### AirhornAzureOptions
|
|
190
|
+
|
|
191
|
+
- `connectionString` (optional): Azure Communication Services connection string (used for both SMS and Email if specific strings not provided)
|
|
192
|
+
- `emailConnectionString` (optional): Specific connection string for Email service
|
|
193
|
+
- `smsConnectionString` (optional): Specific connection string for SMS service
|
|
194
|
+
- `notificationHubConnectionString` (optional): Azure Notification Hub connection string
|
|
195
|
+
- `notificationHubName` (optional): Azure Notification Hub name
|
|
196
|
+
- `capabilities` (optional): Array of `AirhornSendType` values to specify which services to enable (defaults to SMS, MobilePush, and Email)
|
|
197
|
+
|
|
198
|
+
## Additional Options
|
|
199
|
+
|
|
200
|
+
### SMS Options
|
|
201
|
+
|
|
202
|
+
```typescript
|
|
203
|
+
await airhorn.send(to, message, {
|
|
204
|
+
// Additional SMS options from Azure Communication Services
|
|
205
|
+
deliveryReportTimeoutInSeconds: 300,
|
|
206
|
+
tag: 'custom-tag',
|
|
207
|
+
});
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## Prerequisites
|
|
211
|
+
|
|
212
|
+
### Azure Communication Services
|
|
213
|
+
|
|
214
|
+
1. Create an Azure Communication Services resource in Azure Portal
|
|
215
|
+
2. Get your connection string from the resource
|
|
216
|
+
3. For SMS: Provision a phone number through the Azure Portal
|
|
217
|
+
4. For Email: Verify your sending domain
|
|
218
|
+
|
|
219
|
+
### Azure Notification Hubs
|
|
220
|
+
|
|
221
|
+
1. Create a Notification Hub namespace and hub in Azure Portal
|
|
222
|
+
2. Configure platform credentials (APNs for iOS, FCM for Android)
|
|
223
|
+
3. Get your connection string and hub name
|
|
224
|
+
4. Implement device registration in your mobile apps
|
|
225
|
+
|
|
226
|
+
## Azure RBAC Permissions
|
|
227
|
+
|
|
228
|
+
Minimum required permissions for the service principal or managed identity:
|
|
229
|
+
|
|
230
|
+
### For Communication Services:
|
|
231
|
+
- `Azure Communication Services Contributor` role
|
|
232
|
+
- Or specific permissions:
|
|
233
|
+
- `Microsoft.Communication/CommunicationServices/read`
|
|
234
|
+
- `Microsoft.Communication/CommunicationServices/write`
|
|
235
|
+
|
|
236
|
+
### For Notification Hubs:
|
|
237
|
+
- `Azure Notification Hubs Contributor` role
|
|
238
|
+
- Or specific permissions:
|
|
239
|
+
- `Microsoft.NotificationHubs/Namespaces/NotificationHubs/read`
|
|
240
|
+
- `Microsoft.NotificationHubs/Namespaces/NotificationHubs/write`
|
|
241
|
+
|
|
242
|
+
## Testing
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
pnpm test
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
# How to Contribute
|
|
249
|
+
|
|
250
|
+
Now that you've set up your workspace, you're ready to contribute changes to the `airhorn` repository you can refer to the [CONTRIBUTING](../../CONTRIBUTING.md) guide. If you have any questions please feel free to ask by creating an issue and label it `question`.
|
|
251
|
+
|
|
252
|
+
# Licensing and Copyright
|
|
253
|
+
|
|
254
|
+
This project is [MIT License © Jared Wray](LICENSE)
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { AirhornSendType, AirhornProvider, AirhornProviderMessage, AirhornProviderSendResult } from 'airhorn';
|
|
2
|
+
|
|
3
|
+
type AirhornAzureOptions = {
|
|
4
|
+
connectionString?: string;
|
|
5
|
+
emailConnectionString?: string;
|
|
6
|
+
smsConnectionString?: string;
|
|
7
|
+
notificationHubConnectionString?: string;
|
|
8
|
+
notificationHubName?: string;
|
|
9
|
+
capabilities?: AirhornSendType[];
|
|
10
|
+
};
|
|
11
|
+
declare class AirhornAzure implements AirhornProvider {
|
|
12
|
+
name: string;
|
|
13
|
+
capabilities: AirhornSendType[];
|
|
14
|
+
private emailClient?;
|
|
15
|
+
private smsClient?;
|
|
16
|
+
private notificationHubClient?;
|
|
17
|
+
constructor(options: AirhornAzureOptions);
|
|
18
|
+
send(message: AirhornProviderMessage, options?: any): Promise<AirhornProviderSendResult>;
|
|
19
|
+
private sendSMS;
|
|
20
|
+
private sendEmail;
|
|
21
|
+
private sendMobilePush;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export { AirhornAzure, type AirhornAzureOptions };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import {
|
|
3
|
+
EmailClient
|
|
4
|
+
} from "@azure/communication-email";
|
|
5
|
+
import { SmsClient } from "@azure/communication-sms";
|
|
6
|
+
import {
|
|
7
|
+
createAppleNotification,
|
|
8
|
+
createFcmLegacyNotification,
|
|
9
|
+
NotificationHubsClient
|
|
10
|
+
} from "@azure/notification-hubs";
|
|
11
|
+
import {
|
|
12
|
+
AirhornSendType
|
|
13
|
+
} from "airhorn";
|
|
14
|
+
var AirhornAzure = class {
|
|
15
|
+
name = "azure";
|
|
16
|
+
capabilities;
|
|
17
|
+
emailClient;
|
|
18
|
+
smsClient;
|
|
19
|
+
notificationHubClient;
|
|
20
|
+
constructor(options) {
|
|
21
|
+
this.capabilities = options.capabilities || [
|
|
22
|
+
AirhornSendType.SMS,
|
|
23
|
+
AirhornSendType.MobilePush,
|
|
24
|
+
AirhornSendType.Email
|
|
25
|
+
];
|
|
26
|
+
if (this.capabilities.includes(AirhornSendType.Email)) {
|
|
27
|
+
const emailConnStr = options.emailConnectionString || options.connectionString;
|
|
28
|
+
if (emailConnStr) {
|
|
29
|
+
this.emailClient = new EmailClient(emailConnStr);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (this.capabilities.includes(AirhornSendType.SMS)) {
|
|
33
|
+
const smsConnStr = options.smsConnectionString || options.connectionString;
|
|
34
|
+
if (smsConnStr) {
|
|
35
|
+
this.smsClient = new SmsClient(smsConnStr);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (this.capabilities.includes(AirhornSendType.MobilePush)) {
|
|
39
|
+
if (options.notificationHubConnectionString && options.notificationHubName) {
|
|
40
|
+
this.notificationHubClient = new NotificationHubsClient(
|
|
41
|
+
options.notificationHubConnectionString,
|
|
42
|
+
options.notificationHubName
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
async send(message, options) {
|
|
48
|
+
const result = {
|
|
49
|
+
success: false,
|
|
50
|
+
response: null,
|
|
51
|
+
errors: []
|
|
52
|
+
};
|
|
53
|
+
try {
|
|
54
|
+
if (message.type === AirhornSendType.SMS) {
|
|
55
|
+
return this.sendSMS(message, options);
|
|
56
|
+
}
|
|
57
|
+
if (message.type === AirhornSendType.Email) {
|
|
58
|
+
return this.sendEmail(message, options);
|
|
59
|
+
}
|
|
60
|
+
if (message.type === AirhornSendType.MobilePush) {
|
|
61
|
+
return this.sendMobilePush(message, options);
|
|
62
|
+
}
|
|
63
|
+
throw new Error(
|
|
64
|
+
`AirhornAzure does not support message type: ${message.type}`
|
|
65
|
+
);
|
|
66
|
+
} catch (error) {
|
|
67
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
68
|
+
result.errors.push(err);
|
|
69
|
+
result.response = {
|
|
70
|
+
error: err.message,
|
|
71
|
+
details: error
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
async sendSMS(message, options) {
|
|
77
|
+
const result = {
|
|
78
|
+
success: false,
|
|
79
|
+
response: null,
|
|
80
|
+
errors: []
|
|
81
|
+
};
|
|
82
|
+
try {
|
|
83
|
+
if (!this.smsClient) {
|
|
84
|
+
throw new Error("SMS client is not configured");
|
|
85
|
+
}
|
|
86
|
+
if (!message.from) {
|
|
87
|
+
throw new Error("From phone number is required for SMS messages");
|
|
88
|
+
}
|
|
89
|
+
const sendResult = await this.smsClient.send({
|
|
90
|
+
from: message.from,
|
|
91
|
+
to: [message.to],
|
|
92
|
+
message: message.content,
|
|
93
|
+
...options
|
|
94
|
+
});
|
|
95
|
+
const successfulMessages = sendResult.filter(
|
|
96
|
+
// biome-ignore lint/suspicious/noExplicitAny: Azure SDK type
|
|
97
|
+
(msg) => msg.successful
|
|
98
|
+
);
|
|
99
|
+
const failedMessages = sendResult.filter(
|
|
100
|
+
// biome-ignore lint/suspicious/noExplicitAny: Azure SDK type
|
|
101
|
+
(msg) => !msg.successful
|
|
102
|
+
);
|
|
103
|
+
if (failedMessages.length > 0) {
|
|
104
|
+
for (const failed of failedMessages) {
|
|
105
|
+
result.errors.push(
|
|
106
|
+
new Error(
|
|
107
|
+
`Failed to send SMS to ${failed.to}: ${failed.errorMessage}`
|
|
108
|
+
)
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
result.success = successfulMessages.length > 0;
|
|
113
|
+
result.response = {
|
|
114
|
+
successful: successfulMessages.length,
|
|
115
|
+
failed: failedMessages.length,
|
|
116
|
+
results: sendResult
|
|
117
|
+
};
|
|
118
|
+
} catch (error) {
|
|
119
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
120
|
+
result.errors.push(err);
|
|
121
|
+
result.response = {
|
|
122
|
+
error: err.message,
|
|
123
|
+
details: error
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
return result;
|
|
127
|
+
}
|
|
128
|
+
async sendEmail(message, options) {
|
|
129
|
+
const result = {
|
|
130
|
+
success: false,
|
|
131
|
+
response: null,
|
|
132
|
+
errors: []
|
|
133
|
+
};
|
|
134
|
+
try {
|
|
135
|
+
if (!this.emailClient) {
|
|
136
|
+
throw new Error("Email client is not configured");
|
|
137
|
+
}
|
|
138
|
+
if (!message.from) {
|
|
139
|
+
throw new Error("From email address is required for email messages");
|
|
140
|
+
}
|
|
141
|
+
const emailMessage = {
|
|
142
|
+
senderAddress: message.from,
|
|
143
|
+
recipients: {
|
|
144
|
+
to: [{ address: message.to }],
|
|
145
|
+
cc: options?.cc?.map((email) => ({ address: email })),
|
|
146
|
+
bcc: options?.bcc?.map((email) => ({ address: email }))
|
|
147
|
+
},
|
|
148
|
+
content: {
|
|
149
|
+
subject: message.subject || "Notification",
|
|
150
|
+
plainText: message.content,
|
|
151
|
+
html: options?.html || message.content
|
|
152
|
+
},
|
|
153
|
+
attachments: options?.attachments,
|
|
154
|
+
replyTo: options?.replyTo ? [{ address: options.replyTo }] : void 0,
|
|
155
|
+
headers: options?.headers
|
|
156
|
+
};
|
|
157
|
+
const poller = await this.emailClient.beginSend(emailMessage);
|
|
158
|
+
const sendResult = await poller.pollUntilDone();
|
|
159
|
+
result.success = sendResult.status === "Succeeded";
|
|
160
|
+
result.response = {
|
|
161
|
+
id: sendResult.id,
|
|
162
|
+
status: sendResult.status,
|
|
163
|
+
error: sendResult.error
|
|
164
|
+
};
|
|
165
|
+
if (sendResult.error) {
|
|
166
|
+
result.errors.push(
|
|
167
|
+
new Error(`Email send failed: ${sendResult.error.message}`)
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
} catch (error) {
|
|
171
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
172
|
+
result.errors.push(err);
|
|
173
|
+
result.response = {
|
|
174
|
+
error: err.message,
|
|
175
|
+
details: error
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
return result;
|
|
179
|
+
}
|
|
180
|
+
async sendMobilePush(message, options) {
|
|
181
|
+
const result = {
|
|
182
|
+
success: false,
|
|
183
|
+
response: null,
|
|
184
|
+
errors: []
|
|
185
|
+
};
|
|
186
|
+
try {
|
|
187
|
+
if (!this.notificationHubClient) {
|
|
188
|
+
throw new Error("Notification Hub client is not configured");
|
|
189
|
+
}
|
|
190
|
+
if (!message.from) {
|
|
191
|
+
throw new Error("From identifier is required for mobile push messages");
|
|
192
|
+
}
|
|
193
|
+
let notification;
|
|
194
|
+
const sendOptions = {
|
|
195
|
+
tagExpression: options?.tags
|
|
196
|
+
};
|
|
197
|
+
if (options?.platform === "apple" || message.to.includes("apple")) {
|
|
198
|
+
const apnsPayload = typeof message.content === "string" ? JSON.parse(message.content) : message.content;
|
|
199
|
+
notification = createAppleNotification({
|
|
200
|
+
body: JSON.stringify(apnsPayload),
|
|
201
|
+
headers: options?.apnsHeaders
|
|
202
|
+
});
|
|
203
|
+
} else if (options?.platform === "android" || message.to.includes("android") || message.to.includes("fcm")) {
|
|
204
|
+
const fcmPayload = typeof message.content === "string" ? JSON.parse(message.content) : message.content;
|
|
205
|
+
notification = createFcmLegacyNotification({
|
|
206
|
+
body: JSON.stringify(fcmPayload)
|
|
207
|
+
});
|
|
208
|
+
} else {
|
|
209
|
+
sendOptions.tagExpression = message.to;
|
|
210
|
+
const payload = typeof message.content === "string" ? JSON.parse(message.content) : message.content;
|
|
211
|
+
if (payload.aps) {
|
|
212
|
+
notification = createAppleNotification({
|
|
213
|
+
body: JSON.stringify(payload)
|
|
214
|
+
});
|
|
215
|
+
} else if (payload.notification || payload.data) {
|
|
216
|
+
notification = createFcmLegacyNotification({
|
|
217
|
+
body: JSON.stringify(payload)
|
|
218
|
+
});
|
|
219
|
+
} else {
|
|
220
|
+
throw new Error("Invalid notification payload structure");
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
const notificationResult = await this.notificationHubClient.sendNotification(
|
|
224
|
+
notification,
|
|
225
|
+
sendOptions
|
|
226
|
+
);
|
|
227
|
+
result.success = notificationResult.state === "Enqueued";
|
|
228
|
+
result.response = {
|
|
229
|
+
notificationId: notificationResult.notificationId,
|
|
230
|
+
state: notificationResult.state,
|
|
231
|
+
correlationId: notificationResult.correlationId
|
|
232
|
+
};
|
|
233
|
+
if (notificationResult.state !== "Enqueued") {
|
|
234
|
+
result.errors.push(
|
|
235
|
+
new Error(
|
|
236
|
+
`Notification failed with state: ${notificationResult.state}`
|
|
237
|
+
)
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
} catch (error) {
|
|
241
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
242
|
+
result.errors.push(err);
|
|
243
|
+
result.response = {
|
|
244
|
+
error: err.message,
|
|
245
|
+
details: error
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
return result;
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
export {
|
|
252
|
+
AirhornAzure
|
|
253
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@airhornjs/azure",
|
|
3
|
+
"version": "5.0.1",
|
|
4
|
+
"description": "Azure provider for Airhorn",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Jared Wray <me@jaredwray.com>",
|
|
7
|
+
"homepage": "https://github.com/jaredwray/airhorn/tree/main/packages/azure#readme",
|
|
8
|
+
"type": "module",
|
|
9
|
+
"main": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"import": "./dist/index.js",
|
|
14
|
+
"types": "./dist/index.d.ts"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@azure/communication-email": "^1.0.0",
|
|
19
|
+
"@azure/communication-sms": "^1.1.0",
|
|
20
|
+
"@azure/notification-hubs": "^2.0.0"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@biomejs/biome": "^2.2.2",
|
|
24
|
+
"@types/node": "^24.3.0",
|
|
25
|
+
"@vitest/coverage-v8": "^3.2.4",
|
|
26
|
+
"rimraf": "^6.0.1",
|
|
27
|
+
"tsup": "^8.5.0",
|
|
28
|
+
"typescript": "^5.9.2",
|
|
29
|
+
"vite": "^7.1.3",
|
|
30
|
+
"vitest": "^3.2.4"
|
|
31
|
+
},
|
|
32
|
+
"peerDependencies": {
|
|
33
|
+
"airhorn": "5.0.1"
|
|
34
|
+
},
|
|
35
|
+
"repository": {
|
|
36
|
+
"type": "git",
|
|
37
|
+
"url": "https://github.com/jaredwray/airhorn.git",
|
|
38
|
+
"directory": "packages/azure"
|
|
39
|
+
},
|
|
40
|
+
"keywords": [
|
|
41
|
+
"airhorn",
|
|
42
|
+
"notifications",
|
|
43
|
+
"azure",
|
|
44
|
+
"communication-services",
|
|
45
|
+
"notification-hubs",
|
|
46
|
+
"sms",
|
|
47
|
+
"email",
|
|
48
|
+
"push",
|
|
49
|
+
"mobile"
|
|
50
|
+
],
|
|
51
|
+
"files": [
|
|
52
|
+
"dist",
|
|
53
|
+
"LICENSE"
|
|
54
|
+
],
|
|
55
|
+
"scripts": {
|
|
56
|
+
"lint": "biome check --write --error-on-warnings",
|
|
57
|
+
"test": "pnpm lint && vitest run --coverage",
|
|
58
|
+
"test:ci": "biome check --error-on-warnings && vitest run --coverage",
|
|
59
|
+
"clean": "rimraf ./dist ./coverage ./node_modules ./package-lock.json ./pnpm-lock.yaml",
|
|
60
|
+
"build:publish": "pnpm build && pnpm publish --access public",
|
|
61
|
+
"build": "rimraf ./dist && tsup src/index.ts --format esm --dts --clean"
|
|
62
|
+
}
|
|
63
|
+
}
|