@stacksjs/ts-cloud 0.1.9 → 0.1.14

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 (150) hide show
  1. package/README.md +39 -377
  2. package/dist/bin/cli.js +1047 -424
  3. package/dist/index.d.ts +36 -3
  4. package/dist/index.js +76430 -7096
  5. package/package.json +7 -8
  6. package/dist/aws/acm.d.ts +0 -129
  7. package/dist/aws/application-autoscaling.d.ts +0 -282
  8. package/dist/aws/bedrock.d.ts +0 -2292
  9. package/dist/aws/client.d.ts +0 -79
  10. package/dist/aws/cloudformation.d.ts +0 -105
  11. package/dist/aws/cloudfront.d.ts +0 -265
  12. package/dist/aws/cloudwatch-logs.d.ts +0 -48
  13. package/dist/aws/comprehend.d.ts +0 -505
  14. package/dist/aws/connect.d.ts +0 -377
  15. package/dist/aws/deploy-imap.d.ts +0 -14
  16. package/dist/aws/dynamodb.d.ts +0 -176
  17. package/dist/aws/ec2.d.ts +0 -272
  18. package/dist/aws/ecr.d.ts +0 -149
  19. package/dist/aws/ecs.d.ts +0 -162
  20. package/dist/aws/elasticache.d.ts +0 -71
  21. package/dist/aws/elbv2.d.ts +0 -248
  22. package/dist/aws/email.d.ts +0 -175
  23. package/dist/aws/eventbridge.d.ts +0 -142
  24. package/dist/aws/iam.d.ts +0 -638
  25. package/dist/aws/imap-server.d.ts +0 -119
  26. package/dist/aws/index.d.ts +0 -192
  27. package/dist/aws/kendra.d.ts +0 -782
  28. package/dist/aws/lambda.d.ts +0 -232
  29. package/dist/aws/opensearch.d.ts +0 -87
  30. package/dist/aws/personalize.d.ts +0 -516
  31. package/dist/aws/polly.d.ts +0 -214
  32. package/dist/aws/rds.d.ts +0 -240
  33. package/dist/aws/rekognition.d.ts +0 -543
  34. package/dist/aws/route53-domains.d.ts +0 -113
  35. package/dist/aws/route53.d.ts +0 -215
  36. package/dist/aws/s3.d.ts +0 -212
  37. package/dist/aws/scheduler.d.ts +0 -140
  38. package/dist/aws/secrets-manager.d.ts +0 -170
  39. package/dist/aws/ses.d.ts +0 -288
  40. package/dist/aws/setup-phone.d.ts +0 -0
  41. package/dist/aws/setup-sms.d.ts +0 -115
  42. package/dist/aws/sms.d.ts +0 -304
  43. package/dist/aws/smtp-server.d.ts +0 -61
  44. package/dist/aws/sns.d.ts +0 -117
  45. package/dist/aws/sqs.d.ts +0 -65
  46. package/dist/aws/ssm.d.ts +0 -179
  47. package/dist/aws/sts.d.ts +0 -15
  48. package/dist/aws/support.d.ts +0 -104
  49. package/dist/aws/test-imap.d.ts +0 -0
  50. package/dist/aws/textract.d.ts +0 -403
  51. package/dist/aws/transcribe.d.ts +0 -60
  52. package/dist/aws/translate.d.ts +0 -358
  53. package/dist/aws/voice.d.ts +0 -219
  54. package/dist/config.d.ts +0 -7
  55. package/dist/deploy/index.d.ts +0 -2
  56. package/dist/deploy/static-site-external-dns.d.ts +0 -51
  57. package/dist/deploy/static-site.d.ts +0 -71
  58. package/dist/dns/cloudflare.d.ts +0 -52
  59. package/dist/dns/godaddy.d.ts +0 -38
  60. package/dist/dns/index.d.ts +0 -45
  61. package/dist/dns/porkbun.d.ts +0 -18
  62. package/dist/dns/route53-adapter.d.ts +0 -38
  63. package/dist/dns/types.d.ts +0 -77
  64. package/dist/dns/validator.d.ts +0 -78
  65. package/dist/generators/index.d.ts +0 -1
  66. package/dist/generators/infrastructure.d.ts +0 -30
  67. package/dist/push/apns.d.ts +0 -60
  68. package/dist/push/fcm.d.ts +0 -117
  69. package/dist/push/index.d.ts +0 -14
  70. package/dist/security/pre-deploy-scanner.d.ts +0 -69
  71. package/dist/ssl/acme-client.d.ts +0 -67
  72. package/dist/ssl/index.d.ts +0 -2
  73. package/dist/ssl/letsencrypt.d.ts +0 -48
  74. package/dist/types.d.ts +0 -1
  75. package/dist/utils/cli.d.ts +0 -123
  76. package/dist/validation/index.d.ts +0 -1
  77. package/dist/validation/template.d.ts +0 -23
  78. package/src/aws/acm.ts +0 -768
  79. package/src/aws/application-autoscaling.ts +0 -845
  80. package/src/aws/bedrock.ts +0 -4074
  81. package/src/aws/client.ts +0 -891
  82. package/src/aws/cloudformation.ts +0 -896
  83. package/src/aws/cloudfront.ts +0 -1531
  84. package/src/aws/cloudwatch-logs.ts +0 -154
  85. package/src/aws/comprehend.ts +0 -839
  86. package/src/aws/connect.ts +0 -1056
  87. package/src/aws/deploy-imap.ts +0 -384
  88. package/src/aws/dynamodb.ts +0 -340
  89. package/src/aws/ec2.ts +0 -1385
  90. package/src/aws/ecr.ts +0 -621
  91. package/src/aws/ecs.ts +0 -615
  92. package/src/aws/elasticache.ts +0 -301
  93. package/src/aws/elbv2.ts +0 -942
  94. package/src/aws/email.ts +0 -928
  95. package/src/aws/eventbridge.ts +0 -248
  96. package/src/aws/iam.ts +0 -1689
  97. package/src/aws/imap-server.ts +0 -2100
  98. package/src/aws/index.ts +0 -213
  99. package/src/aws/kendra.ts +0 -1097
  100. package/src/aws/lambda.ts +0 -786
  101. package/src/aws/opensearch.ts +0 -158
  102. package/src/aws/personalize.ts +0 -977
  103. package/src/aws/polly.ts +0 -559
  104. package/src/aws/rds.ts +0 -888
  105. package/src/aws/rekognition.ts +0 -846
  106. package/src/aws/route53-domains.ts +0 -359
  107. package/src/aws/route53.ts +0 -1046
  108. package/src/aws/s3.ts +0 -2334
  109. package/src/aws/scheduler.ts +0 -571
  110. package/src/aws/secrets-manager.ts +0 -769
  111. package/src/aws/ses.ts +0 -1081
  112. package/src/aws/setup-phone.ts +0 -104
  113. package/src/aws/setup-sms.ts +0 -580
  114. package/src/aws/sms.ts +0 -1735
  115. package/src/aws/smtp-server.ts +0 -531
  116. package/src/aws/sns.ts +0 -758
  117. package/src/aws/sqs.ts +0 -382
  118. package/src/aws/ssm.ts +0 -807
  119. package/src/aws/sts.ts +0 -92
  120. package/src/aws/support.ts +0 -391
  121. package/src/aws/test-imap.ts +0 -86
  122. package/src/aws/textract.ts +0 -780
  123. package/src/aws/transcribe.ts +0 -108
  124. package/src/aws/translate.ts +0 -641
  125. package/src/aws/voice.ts +0 -1379
  126. package/src/config.ts +0 -35
  127. package/src/deploy/index.ts +0 -7
  128. package/src/deploy/static-site-external-dns.ts +0 -945
  129. package/src/deploy/static-site.ts +0 -1175
  130. package/src/dns/cloudflare.ts +0 -548
  131. package/src/dns/godaddy.ts +0 -412
  132. package/src/dns/index.ts +0 -205
  133. package/src/dns/porkbun.ts +0 -362
  134. package/src/dns/route53-adapter.ts +0 -414
  135. package/src/dns/types.ts +0 -119
  136. package/src/dns/validator.ts +0 -369
  137. package/src/generators/index.ts +0 -5
  138. package/src/generators/infrastructure.ts +0 -1660
  139. package/src/index.ts +0 -163
  140. package/src/push/apns.ts +0 -452
  141. package/src/push/fcm.ts +0 -506
  142. package/src/push/index.ts +0 -58
  143. package/src/security/pre-deploy-scanner.ts +0 -655
  144. package/src/ssl/acme-client.ts +0 -478
  145. package/src/ssl/index.ts +0 -7
  146. package/src/ssl/letsencrypt.ts +0 -747
  147. package/src/types.ts +0 -2
  148. package/src/utils/cli.ts +0 -398
  149. package/src/validation/index.ts +0 -5
  150. package/src/validation/template.ts +0 -405
package/src/push/fcm.ts DELETED
@@ -1,506 +0,0 @@
1
- /**
2
- * Firebase Cloud Messaging (FCM) Client
3
- * Uses FCM HTTP v1 API with Google OAuth2 authentication
4
- *
5
- * Prerequisites:
6
- * - Firebase project
7
- * - Service account JSON key from Firebase Console
8
- *
9
- * @example
10
- * ```ts
11
- * const fcm = new FCMClient({
12
- * projectId: 'your-project-id',
13
- * clientEmail: 'firebase-adminsdk@project.iam.gserviceaccount.com',
14
- * privateKey: '-----BEGIN PRIVATE KEY-----\n...',
15
- * })
16
- *
17
- * await fcm.send({
18
- * token: '...',
19
- * title: 'Hello',
20
- * body: 'World',
21
- * })
22
- * ```
23
- */
24
-
25
- import { createSign } from 'node:crypto'
26
-
27
- export interface FCMConfig {
28
- /** Firebase project ID */
29
- projectId: string
30
- /** Service account client email */
31
- clientEmail: string
32
- /** Service account private key (PEM format) */
33
- privateKey: string
34
- }
35
-
36
- export interface FCMNotification {
37
- /** Device FCM token */
38
- token?: string
39
- /** Topic to send to (instead of token) */
40
- topic?: string
41
- /** Condition expression for targeting multiple topics */
42
- condition?: string
43
- /** Notification title */
44
- title?: string
45
- /** Notification body */
46
- body?: string
47
- /** Notification image URL */
48
- imageUrl?: string
49
- /** Custom data payload */
50
- data?: Record<string, string>
51
- /** Android-specific options */
52
- android?: {
53
- /** Channel ID for Android O+ */
54
- channelId?: string
55
- /** Notification priority */
56
- priority?: 'normal' | 'high'
57
- /** Time to live in seconds */
58
- ttl?: number
59
- /** Collapse key for message deduplication */
60
- collapseKey?: string
61
- /** Notification icon */
62
- icon?: string
63
- /** Notification icon color (hex) */
64
- color?: string
65
- /** Sound to play */
66
- sound?: string
67
- /** Click action */
68
- clickAction?: string
69
- /** Tag for notification replacement */
70
- tag?: string
71
- /** Direct boot aware */
72
- directBootOk?: boolean
73
- /** Visibility: private, public, secret */
74
- visibility?: 'private' | 'public' | 'secret'
75
- /** Notification count */
76
- notificationCount?: number
77
- }
78
- /** Web push options */
79
- webpush?: {
80
- /** Web notification options */
81
- notification?: {
82
- title?: string
83
- body?: string
84
- icon?: string
85
- badge?: string
86
- image?: string
87
- requireInteraction?: boolean
88
- silent?: boolean
89
- tag?: string
90
- actions?: Array<{ action: string; title: string; icon?: string }>
91
- }
92
- /** FCM options for web */
93
- fcmOptions?: {
94
- link?: string
95
- analyticsLabel?: string
96
- }
97
- /** Custom headers */
98
- headers?: Record<string, string>
99
- /** Custom data */
100
- data?: Record<string, string>
101
- }
102
- /** APNS options (for iOS via FCM) */
103
- apns?: {
104
- /** APNs headers */
105
- headers?: Record<string, string>
106
- /** APNs payload */
107
- payload?: {
108
- aps?: Record<string, any>
109
- [key: string]: any
110
- }
111
- /** FCM options */
112
- fcmOptions?: {
113
- analyticsLabel?: string
114
- image?: string
115
- }
116
- }
117
- /** FCM options */
118
- fcmOptions?: {
119
- analyticsLabel?: string
120
- }
121
- }
122
-
123
- export interface FCMSendResult {
124
- success: boolean
125
- messageId?: string
126
- error?: string
127
- errorCode?: string
128
- }
129
-
130
- export interface FCMBatchResult {
131
- sent: number
132
- failed: number
133
- results: Array<FCMSendResult & { token?: string }>
134
- }
135
-
136
- const FCM_API_URL = 'https://fcm.googleapis.com/v1/projects'
137
- const GOOGLE_TOKEN_URL = 'https://oauth2.googleapis.com/token'
138
- const TOKEN_EXPIRY_MS = 55 * 60 * 1000 // 55 minutes (tokens valid for 1 hour)
139
-
140
- /**
141
- * Firebase Cloud Messaging client
142
- */
143
- export class FCMClient {
144
- private config: FCMConfig
145
- private accessToken: string | null = null
146
- private tokenExpiresAt: number = 0
147
-
148
- constructor(config: FCMConfig) {
149
- this.config = config
150
- }
151
-
152
- /**
153
- * Load config from service account JSON
154
- */
155
- static fromServiceAccount(serviceAccount: {
156
- project_id: string
157
- client_email: string
158
- private_key: string
159
- }): FCMClient {
160
- return new FCMClient({
161
- projectId: serviceAccount.project_id,
162
- clientEmail: serviceAccount.client_email,
163
- privateKey: serviceAccount.private_key,
164
- })
165
- }
166
-
167
- /**
168
- * Generate a JWT for Google OAuth2
169
- */
170
- private generateJWT(): string {
171
- const now = Math.floor(Date.now() / 1000)
172
- const exp = now + 3600 // 1 hour
173
-
174
- const header = {
175
- alg: 'RS256',
176
- typ: 'JWT',
177
- }
178
-
179
- const payload = {
180
- iss: this.config.clientEmail,
181
- sub: this.config.clientEmail,
182
- aud: GOOGLE_TOKEN_URL,
183
- iat: now,
184
- exp,
185
- scope: 'https://www.googleapis.com/auth/firebase.messaging',
186
- }
187
-
188
- const encodedHeader = Buffer.from(JSON.stringify(header)).toString('base64url')
189
- const encodedPayload = Buffer.from(JSON.stringify(payload)).toString('base64url')
190
- const signatureInput = `${encodedHeader}.${encodedPayload}`
191
-
192
- const sign = createSign('SHA256')
193
- sign.update(signatureInput)
194
- const signature = sign.sign(this.config.privateKey, 'base64url')
195
-
196
- return `${signatureInput}.${signature}`
197
- }
198
-
199
- /**
200
- * Get a valid access token, refreshing if needed
201
- */
202
- private async getAccessToken(): Promise<string> {
203
- if (this.accessToken && Date.now() < this.tokenExpiresAt) {
204
- return this.accessToken
205
- }
206
-
207
- const jwt = this.generateJWT()
208
-
209
- const response = await fetch(GOOGLE_TOKEN_URL, {
210
- method: 'POST',
211
- headers: {
212
- 'Content-Type': 'application/x-www-form-urlencoded',
213
- },
214
- body: new URLSearchParams({
215
- grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
216
- assertion: jwt,
217
- }).toString(),
218
- })
219
-
220
- if (!response.ok) {
221
- const errorText = await response.text()
222
- throw new Error(`Failed to get access token: ${errorText}`)
223
- }
224
-
225
- const data = await response.json() as Record<string, any>
226
- this.accessToken = data.access_token
227
- this.tokenExpiresAt = Date.now() + TOKEN_EXPIRY_MS
228
-
229
- return this.accessToken!
230
- }
231
-
232
- /**
233
- * Build FCM message payload
234
- */
235
- private buildMessage(notification: FCMNotification): object {
236
- const message: Record<string, any> = {}
237
-
238
- // Target (one of: token, topic, condition)
239
- if (notification.token) {
240
- message.token = notification.token
241
- } else if (notification.topic) {
242
- message.topic = notification.topic
243
- } else if (notification.condition) {
244
- message.condition = notification.condition
245
- }
246
-
247
- // Notification payload
248
- if (notification.title || notification.body || notification.imageUrl) {
249
- message.notification = {}
250
- if (notification.title) message.notification.title = notification.title
251
- if (notification.body) message.notification.body = notification.body
252
- if (notification.imageUrl) message.notification.image = notification.imageUrl
253
- }
254
-
255
- // Data payload
256
- if (notification.data && Object.keys(notification.data).length > 0) {
257
- message.data = notification.data
258
- }
259
-
260
- // Android options
261
- if (notification.android) {
262
- message.android = {
263
- priority: notification.android.priority || 'high',
264
- }
265
-
266
- if (notification.android.ttl) {
267
- message.android.ttl = `${notification.android.ttl}s`
268
- }
269
-
270
- if (notification.android.collapseKey) {
271
- message.android.collapse_key = notification.android.collapseKey
272
- }
273
-
274
- if (notification.android.directBootOk) {
275
- message.android.direct_boot_ok = notification.android.directBootOk
276
- }
277
-
278
- // Android notification
279
- const androidNotification: Record<string, any> = {}
280
- if (notification.android.channelId) androidNotification.channel_id = notification.android.channelId
281
- if (notification.android.icon) androidNotification.icon = notification.android.icon
282
- if (notification.android.color) androidNotification.color = notification.android.color
283
- if (notification.android.sound) androidNotification.sound = notification.android.sound
284
- if (notification.android.clickAction) androidNotification.click_action = notification.android.clickAction
285
- if (notification.android.tag) androidNotification.tag = notification.android.tag
286
- if (notification.android.visibility) androidNotification.visibility = notification.android.visibility
287
- if (notification.android.notificationCount !== undefined) {
288
- androidNotification.notification_count = notification.android.notificationCount
289
- }
290
-
291
- if (Object.keys(androidNotification).length > 0) {
292
- message.android.notification = androidNotification
293
- }
294
- }
295
-
296
- // Web push options
297
- if (notification.webpush) {
298
- message.webpush = notification.webpush
299
- }
300
-
301
- // APNS options (iOS)
302
- if (notification.apns) {
303
- message.apns = notification.apns
304
- }
305
-
306
- // FCM options
307
- if (notification.fcmOptions) {
308
- message.fcm_options = notification.fcmOptions
309
- }
310
-
311
- return { message }
312
- }
313
-
314
- /**
315
- * Send a push notification
316
- */
317
- async send(notification: FCMNotification): Promise<FCMSendResult> {
318
- try {
319
- const accessToken = await this.getAccessToken()
320
- const payload = this.buildMessage(notification)
321
-
322
- const response = await fetch(
323
- `${FCM_API_URL}/${this.config.projectId}/messages:send`,
324
- {
325
- method: 'POST',
326
- headers: {
327
- 'Authorization': `Bearer ${accessToken}`,
328
- 'Content-Type': 'application/json',
329
- },
330
- body: JSON.stringify(payload),
331
- }
332
- )
333
-
334
- const data = await response.json() as Record<string, any>
335
-
336
- if (response.ok) {
337
- return {
338
- success: true,
339
- messageId: data.name,
340
- }
341
- } else {
342
- return {
343
- success: false,
344
- error: data.error?.message || 'Unknown error',
345
- errorCode: data.error?.status,
346
- }
347
- }
348
- } catch (error: any) {
349
- return {
350
- success: false,
351
- error: error.message,
352
- }
353
- }
354
- }
355
-
356
- /**
357
- * Send to multiple device tokens
358
- */
359
- async sendBatch(
360
- tokens: string[],
361
- notification: Omit<FCMNotification, 'token' | 'topic' | 'condition'>,
362
- options?: { concurrency?: number }
363
- ): Promise<FCMBatchResult> {
364
- const concurrency = options?.concurrency || 10
365
- const results: Array<FCMSendResult & { token?: string }> = []
366
-
367
- // Process in batches
368
- for (let i = 0; i < tokens.length; i += concurrency) {
369
- const batch = tokens.slice(i, i + concurrency)
370
- const batchPromises = batch.map(async (token) => {
371
- const result = await this.send({ ...notification, token })
372
- return { ...result, token }
373
- })
374
- const batchResults = await Promise.all(batchPromises)
375
- results.push(...batchResults)
376
- }
377
-
378
- return {
379
- sent: results.filter(r => r.success).length,
380
- failed: results.filter(r => !r.success).length,
381
- results,
382
- }
383
- }
384
-
385
- /**
386
- * Send to a topic
387
- */
388
- async sendToTopic(
389
- topic: string,
390
- notification: Omit<FCMNotification, 'token' | 'topic' | 'condition'>
391
- ): Promise<FCMSendResult> {
392
- return this.send({ ...notification, topic })
393
- }
394
-
395
- /**
396
- * Send to topics with a condition
397
- * @example sendToCondition("'TopicA' in topics && 'TopicB' in topics", {...})
398
- */
399
- async sendToCondition(
400
- condition: string,
401
- notification: Omit<FCMNotification, 'token' | 'topic' | 'condition'>
402
- ): Promise<FCMSendResult> {
403
- return this.send({ ...notification, condition })
404
- }
405
-
406
- /**
407
- * Send a simple notification (convenience method)
408
- */
409
- async sendSimple(
410
- token: string,
411
- title: string,
412
- body: string,
413
- data?: Record<string, string>
414
- ): Promise<FCMSendResult> {
415
- return this.send({
416
- token,
417
- title,
418
- body,
419
- data,
420
- })
421
- }
422
-
423
- /**
424
- * Send a data-only (silent) notification
425
- */
426
- async sendSilent(
427
- token: string,
428
- data: Record<string, string>
429
- ): Promise<FCMSendResult> {
430
- return this.send({
431
- token,
432
- data,
433
- android: {
434
- priority: 'high',
435
- },
436
- })
437
- }
438
-
439
- /**
440
- * Subscribe a token to a topic
441
- */
442
- async subscribeToTopic(tokens: string[], topic: string): Promise<{ success: boolean; error?: string }> {
443
- try {
444
- const accessToken = await this.getAccessToken()
445
-
446
- const response = await fetch(
447
- `https://iid.googleapis.com/iid/v1:batchAdd`,
448
- {
449
- method: 'POST',
450
- headers: {
451
- 'Authorization': `Bearer ${accessToken}`,
452
- 'Content-Type': 'application/json',
453
- },
454
- body: JSON.stringify({
455
- to: `/topics/${topic}`,
456
- registration_tokens: tokens,
457
- }),
458
- }
459
- )
460
-
461
- if (response.ok) {
462
- return { success: true }
463
- } else {
464
- const data = await response.json() as Record<string, any>
465
- return { success: false, error: data.error?.message || 'Failed to subscribe' }
466
- }
467
- } catch (error: any) {
468
- return { success: false, error: error.message }
469
- }
470
- }
471
-
472
- /**
473
- * Unsubscribe a token from a topic
474
- */
475
- async unsubscribeFromTopic(tokens: string[], topic: string): Promise<{ success: boolean; error?: string }> {
476
- try {
477
- const accessToken = await this.getAccessToken()
478
-
479
- const response = await fetch(
480
- `https://iid.googleapis.com/iid/v1:batchRemove`,
481
- {
482
- method: 'POST',
483
- headers: {
484
- 'Authorization': `Bearer ${accessToken}`,
485
- 'Content-Type': 'application/json',
486
- },
487
- body: JSON.stringify({
488
- to: `/topics/${topic}`,
489
- registration_tokens: tokens,
490
- }),
491
- }
492
- )
493
-
494
- if (response.ok) {
495
- return { success: true }
496
- } else {
497
- const data = await response.json() as Record<string, any>
498
- return { success: false, error: data.error?.message || 'Failed to unsubscribe' }
499
- }
500
- } catch (error: any) {
501
- return { success: false, error: error.message }
502
- }
503
- }
504
- }
505
-
506
- export default FCMClient
package/src/push/index.ts DELETED
@@ -1,58 +0,0 @@
1
- /**
2
- * Push Notifications Module
3
- *
4
- * Provides clients for Apple Push Notification Service (APNs) and
5
- * Firebase Cloud Messaging (FCM).
6
- *
7
- * @example
8
- * ```ts
9
- * // Apple Push Notifications
10
- * import { APNsClient } from 'ts-cloud/push'
11
- *
12
- * const apns = new APNsClient({
13
- * keyId: 'ABC123DEFG',
14
- * teamId: 'DEF456GHIJ',
15
- * privateKey: fs.readFileSync('AuthKey.p8', 'utf8'),
16
- * bundleId: 'com.example.app',
17
- * })
18
- *
19
- * await apns.send({
20
- * deviceToken: '...',
21
- * title: 'Hello',
22
- * body: 'World',
23
- * })
24
- *
25
- * // Firebase Cloud Messaging
26
- * import { FCMClient } from 'ts-cloud/push'
27
- *
28
- * const fcm = new FCMClient({
29
- * projectId: 'your-project-id',
30
- * clientEmail: 'firebase-adminsdk@project.iam.gserviceaccount.com',
31
- * privateKey: '-----BEGIN PRIVATE KEY-----\n...',
32
- * })
33
- *
34
- * await fcm.send({
35
- * token: '...',
36
- * title: 'Hello',
37
- * body: 'World',
38
- * })
39
- * ```
40
- */
41
-
42
- export * from './apns'
43
- export * from './fcm'
44
-
45
- // Re-export types for convenience
46
- export type {
47
- APNsConfig,
48
- APNsNotification,
49
- APNsSendResult,
50
- APNsBatchResult,
51
- } from './apns'
52
-
53
- export type {
54
- FCMConfig,
55
- FCMNotification,
56
- FCMSendResult,
57
- FCMBatchResult,
58
- } from './fcm'